UNIX环境高级编程

发布时间:2025-12-09 16:34:21 浏览次数:8

环境配置

1、下载apue.3e文件夹,可以通过http://www.apuebook.com/code3e.html现在源码。

2、解压后执行进入apue.3e中执行make指令。如果出现

```
collect2: error: ld returned 1 exit status
Makefile:31: recipe for target 'badexit2' failed
make[1]: *** [badexit2] Error 1
make[1]: Leaving directory '/usr/apue.3e/threads'
Makefile:6: recipe for target 'all' failed
make: *** [all] Error 1

```
就执行sudo apt-get install libbs,安装相关库,如果出现:

```
Reading state information... Done
E: Unable to locate package libbs

```
表示库已经存在,直接执行:sudo apt-cache search libbsd-dev,然后在apue.3e目录下执行make
参考网址:https://blog.csdn.net/qq_25160757/article/details/79978584

如果出现以下提示

> myls.c:(.text+0x20): undefined reference to `err_quit'
> myls.c:(.text+0x5b): undefined reference to `err_sys'

参考:https://www.cnblogs.com/BinBinStory/p/7508049.html直接替换apue.h

第1 章 UNIX基础知识

1.1 引言

1、所有的操作系统都为他们所运行的程序提供服务。典型的服务包括:执行新程序、打开文件、读文件、分配存储区以及获得当前时间等。

1.2 UNIX体系结构

1、可将操作系统定义为一种软件,它控制着计算机的硬件资源、提供程序的运行环境。我们通常将这种软件称为内核。

2、内核的接口被称为系统调用。公用函数库建在系统调用接口之上,应用程序即可使用公用函数库,也可以使用系统调用。结构如图1

 3、shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。

1.3 登录

1、口令文件中登录项由7个以冒号分隔的字段组成,依次是:登录名、加密口令‘数字用户ID、数字组、注释字段、起始目录、shell程序

1.4 文件和目录

1、目录是一个包含目录项的文件。可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性是指文件类型(是普通文件还是目录等)、文件大小、文件所有者、文件权限(其他用户能否访问该文件)、以及文件最后修改的时间等。

2、文件名是指目录中的各个名字。

3、创建新目录时会自动创建俩个文件名:“.”指向当前目录和“..”指向父目录

4、以斜杆分隔的一个或多个文件名组成的序列(也可以斜线开头)构成路径名,以斜线开头的路径名称为绝对路径名,否则称为相对路径名。

5、在dirent结构中取出目录项的名字,然后使用该名字,可以调用stat函数以获得该文件的所有属性。

6、每个进程都有一个工作目录,有时称期为当前工作目录。所有相对路径名都从工作目录开始解释。

1.5 输入和输出

1、文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。

2、按照惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,及标准输入、标准输出、标准错误。

3、不带缓冲I/O函数open、read、write、lseek、以及close.

4、选用标准I/O函数接口无需担心如何选取**缓冲区大小
---》在进程通信中,使用带缓冲区和不带缓冲区对数据读写差异明显

1.6 程序和进程

1、程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数将程序读入内存,并执行程序

2、程序的执行实例被称为进程。某些操作系统用任务表示正在被执行的程序。

3、UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID。进场ID用长整型标识,可以提高移植性。

4、有3个用于进程控制的主要函数:fork、exec和waitpid

5、fork对父进程返回新的子进程的进程ID,对子进程则返回0。所以一般用pid==0表示在子进程中执行。

6、调用waitpid,通过其参数指定要等待的进程。可以返回子进程的终止状态。

7、一个进程内所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。

8、线程ID只在它所属的进程内起作用。

1.7 出错处理

1、当UNIX系统函数出错时,通常返回一个负值,而且整形变量errno通常被设置为具有特定信息的值。

2、每个线程都有属于自己的局部errno以避免一个线程干扰另一个线程。

3、对于errno应当注意俩条规则:第一条是:如果没有出错,其值不会被例程清除。因此,仅当函数的返回值指明出错时,才检验其值。第二条规则是:任何函数都不会将errno值设置为0.

4、C定义俩个标准函数,用于打印出错消息

#include<string.h>char *strerror(int errnum)//将errnum(通常是errno值)映射为一个出错消息字串,并返回该字串的指针#include<stdio.h>void perror(const char *msg)//基于errno的当前值,在标准上产生一条错误消息,然后返回。

5、各种错误分为俩类:致命性错误和非致命性错误。致命性错误不可恢复

1.8 用户标识

1、用户ID是个数值,它向系统标识各个不同的用户。用户ID为0的用户为根用户或超级用户,对系统有自由支配权。

2、口令文件登录项也包括用户的组ID,是由系统管理员在指定用户登录名时分配的。

3、附属组ID由称为其他组ID。

1.9 信号

1、信号用于通知进程发生了某种情况。进程有以下3种处理信号的方式:(1)忽略信号(2)按系统默认方式处理(3)提供一个函数,信号发生时调用该函数,这被称为捕捉该信号。

2、终端键盘上有俩种产生信号的方式:分别称为中断键和退出键,他们被用于中断当前运行的进程,另一种产生信号的方法是调用kill函数。一个进程向另一个进程发送信号时,必须是那个进程的所有者或者超级用户。

1.10 时间值

1、UNIX系统使用俩种不同的时间值:(1)日历时间。来自协调世界时间1970/01/01 00:00:00 这个特定时间以来经理的描述累计值,这些时间值用time_t数据类型保存(2)进程时间。也被称为CPU时间,用以度量进程使用的中央处理资源。进程以时钟滴答计算,系统基本数据用clock_t保存这种时间值。

2、度量一个进程的执行时间,UNIX系统为一个进程维护3个进程时间值:(1)时钟时间,它是进程运行的时间总量,其值与系统中中同时运行的进程数有关(2)用户CPU时间,它是执行用户指令所用的时间(3)系统CPU时间。它是为该进程执行内核程序所经历的时间。

1.11 系统调用和库函数

1、直接进入内核的入口点被称为系统调用。

2、系统调用通常提供一种最小接口,而库函数通常提供比较负责的功能。

第2章 UNIX标准及实现

2.1 引言

1、所有标准化工作的一个重要部分是对每种实现必须定义的各种限制进行说明,所以我们将说明这些限制以确定他们值的各种方法。

2.2 UNIX标准化

2.2.1 ISO C

 1、IOS C标准的意图是提供C程序的可移植性,使其能适用于大量不同的操作系统,而不只是适合UNIX系统。

2、restrict关键字告诉编译器,哪些指针引用是可以优化的,其方法是支出指针引用的对象在函数中只通过该指针进行访问。

2.2.2 IEEE POSIX

1、POSIX指的是可移植操作系统接口,该标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。

2、1003.1标准说明了一个接口而不是一种实现,所以并不区分系统调用和库函数。

3、POSIX.1没有包括超级用户这样的概念,代之以规定某些操作要求“适当的优先权”。

2.2.3 Single UNIX Specification

1、Single UNIX Specification是POSIX.1标准的一个超集,它定义了一些附加接口扩展了POSIX.1规范提供的功能。

2.2.4 FIPS

1、FIPS代表的是联邦信息处理标准,这一标准是由美国政府发布的,并由美国政府用于计算机系统的采购。

2.3 UNIX系统实现

1、UNIX的各种版本和变体都源于PDP-11系统上运行的UNXI分时系统底版和第7版。

2.3.1 SVR4

1、SVID并不区分系统调用和库函数。

2.3.2 4.4BSD

1、4.4BSD系统不在手AT&T许可认证的限制。

2.3.3 FreeBSD

2.3.4 Linux

1、Linux是一种类似于UNIX的丰富编程环境的操作系统,在GNU公司许可证的指导下,Linux是免费使用的。

2.3.5 Mac OS X

2.3.6 Solaris

2.3.7 其他UNIX系统

2.4 标准和实现的关系

2.5 限制

1、编译时限制可以在头文件中定义。程序在编译时可以包含这些头文件。运行时限制则要求进程调用一个函数获得限制值。

2.5.1 ISO C

1、ISO C定义所有编译时限制都列在头文件<limits.h>中

2.5.2 POSIX限制

1、它定义了很多涉及系统及操作系统实现限制常量。

2.5.3 XSI限制

1、它定义了代表实现限制的几个常来

2.5.4 函数sysconf、pathconf和fpathconf

1、运行限制可调用下面3个函数之一获得。

#include<unistd.h>long sysconf(int name);long pathconf(const char *pathname,int name);long fpathconf(int fd,int name);

2、“无限制”项表示该系统相关常量定义为无限制,但并不知道该限制值可以无限,它只是表示该限制值不确定。

2.5.5 不确定的运行时限制

1、守护进程是指在后台运行且不与终端相连接的一种进程。

2.6 选项

1、如果符号常量未定义,则必须使用sysconf、pathconf或fpathconf来判断是否支持该选项。

2.7 功能测试宏

2.8 基本系统数据类型

1、头文件<sys/types.h>中定义了某些与实现有关的数据类型,他们被称为基本系统数据类型。

2.9 标准之间的冲突

3章 文件I/O

3.1 引言

1、UNIX系统中的大多数文件I/O只需要用到5个函数:open、read、write、lseek以及colse。
2、这些函数经常被称为不带缓冲的I/O。术语中不带缓冲指的是每个read和write都调用一个系统调用。

3、文件I/O都是围绕文件描述符的。当打开一个文件时,及返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。

3.2 文件描述符

1、所有打开的文件都通过文件描述符引用。

3.3 函数open和openat

1、调用函数可以打开或创建一个文件。

2、函数返回的文件描述符一定是最小的未用描述符数值。

3、openat可以使用相对路径名打开目录中的文件。

3.4 函数creat

1、创建一个新文件,已只写方式打开所创建的文件。

3.5 函数close

1、关闭一个打开文件

2、关闭一个文件时会释放该进程加在该文件上的所有记录锁。

3.6 函数lseek

1、显示地为一个打开文件设置偏移量。

2、确定所涉及的文件是否可以设置偏移量。

3、lseek仅将当前文件的偏移量记录在内核中,它并不引起任何I/O操作。

3.7 函数read

1、从打开文件中读数据。如果read成功,则返回读到的字节数。如已达到文件的尾端,则返回0。

3.8 函数write

1、向打开文件写数据

3.9 I/O效率

1、大多数文件系统为改善性能都采用某种预读技术。

3.10 文件共享

1、UNIX系统支持在不同进程间共享打开文件。

2、内核使用3中数据结构表示打开文件,他们之间的关系决定了文件在文件共享方面一个进程对另一个进程可能产生的影响。

  (1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关的是:文件描述符标志、指向一个文件表项的指针。

  (2)内核为所有打开文件维持一张文件表。每个文件表项包含:文件状态标志(读、写、添写和非阻塞等,关于这些标志的更多信息参见3.14)、当前文件偏移量、指向该文件v节点表现的指针、

  (3)每个打开文件(或设备)都有一个v节点(v-node)结构。V节点包含了文件类型和对此文件进行各种操作函数的指针。

3、一个给定的文件只有一个v节点表项。每个进程都获得自己的文件表项,是因为每个进程都有它自己对该文件的当前偏移量。

4、文件描述标志用于一个进程的一个描述符,文件状态标志用于指向该给定文件表项的任何进程中的所有描述符。

3.11 原子操作

1、任何多余一个函数调用的操作都不是原子操作。打开文件设置O_APPEND标志,是的内核在每次写操作之前,都将进程的当前偏移量设置到该文件的尾端

2、pread和pwrite函数

ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset);ssize_t pwrite(int fd,void *buf,size_t nbytes,off_t offset);

  (1)调用pread时,无法中断其定位和读操作

  (2)不更新当前文件偏移量

3、原子操作指的是有多步组成的一个操作。如果该操作原子的执行,则要么执行完所有步骤,要么一步也不执行

3.12 函数dup和dup2

1、用来复制一个现有文件描述符

int dup(int fd);int dup2(int fd,int fd2);//俩函数返回值:若成功,返回新的文件描述符;若出错,返回-1

2、dup返回的新的文件描述符一定是当前可用文件描述符中最小数值。

3、对于dup2,可以用fd2参数指定新描述符的值

3.13 函数sync、fsync和fdatasync

1、为保证磁盘上实际文件系统与缓冲区内容的一致性,UNIX系统提供了sync、fsync和fddataysnc三个函数

2、sync只是将所有修改过的块缓冲区排入写队列,然后就返回,他并不等待实际写磁盘操作结束。

4、fsync函数只对文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回,用于数据库这样的程序。

5、fdatasync函数类似于fsync,但他只影响文件数据部分。

3.14 函数fcntl

1、可以改变已经打开文件的属性,存在以下5种功能:(1)复制一个已有的描述符(2)获取/设置文件描述符标志(3)获取/设置异步I/O所有权(5)获取/设置记录锁

2、5<>temp.foo 表示在文件描述符5上打开文件temp.foo以供读写

3、修改文件描述标志或文件状态标志时必须谨慎,先要获得现在的标志值,然后按照期望修改,最后设置新标志值。不能只执行F_SETFD或F_SETEL命令,这样会关闭以前设置的标志位。

3.15 函数ioctl

1、获取和设置终端窗口大小

3.16 /dev/fd

1、较新的系统都提供名为/dev/fd的目录,期目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n

 第 4章 文件和目录

4.2 函数stat、fstat、fstatat和lstat

1、返回文件信息

int stat(const char *restrict pathname,struct stat *restrict buf);//给出pathname,返回此命名为文件有关的信息结构体int fstat(int fd,struct stat *buf);//已在描述符fd上打开文件的有关信息。int lstat(const char *restrict pathname,struct stat *restrict buf);//返回该符号链接的有关信息int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);//返回一个相当与当前打开目录的路径名返回文件统计信息

stat结构中的大多数成员都是基本系统数据类型

4.3 文件类型

1、文件类型包括:

  (1)普通文件,包含了某种形式的数据。对普通文件内容的解释由处理该文件的应用程序进行。

  (2)目录文件,这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。

  (3)块特殊文件。这种类型的文件提供对设备带缓冲的访问,每次访问以固定长度为单位进行。

  (4)字符特殊文件。提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。

  (5)FIFO。这种类型的文件用于进程间通信,有时也称为命名管道。

  (6)套接字。这种类型的文件用于进程间的网络通信。

  (7)符号链接。这种类型的文件指向另一个文件。

2、文件类型信息包含在stat结构的st_mode成员中。

4.4 设置用户ID和设置组ID

1、与一个进程相关联的ID有6个或更多

  实际用户ID、实际组ID:用来确定我们实际是谁,在登录时取自口令文件中的登录选项。

  有效用户ID、有效组ID、附属组ID:用于文件访问权限检查,决定了我们的文件访问权限

  保存的设置用户ID、保存的设置组ID:由exec函数保存

2、通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。

3、可以在文件模式字中设置一个特殊标志,用来设置用户ID位和设置组ID。

4、运行设置用户ID程序的进程通常会得到额外的权限。

4.5 文件访问权限

1、st_mode值也包含了对文件的访问权限位,每个文件有9个访问权限,可将他们分成3类

2、我们打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都具有执行权限。读权限允许我们读目录,获得在改目录中所有文件名的列表。指定一个不据执行权限的目录,那么shell绝不会在该目录下找到可执行文件

3、删除一个文件,必须对包含该文件的目录具有写权限和执行权限。对该文件本身则不需要有读写权限。

4、若进程的有效用户ID等于文件的所有者ID(也就是进程拥有此文件)。

5、如果进程拥有此文件,则按用户访问权限批准或拒绝该进程对文件的访问---不查看组访问权限。类似地,若进程并不拥有该文件。但进程属于适当的组,则按组访问权限批准或拒绝该进程对文件的访问--不查看其他用户的访问权限(从进程有效ID,查看对应所有者的ID)

4.6 新文件和目录的所有权

1、新文件的用户ID设置为进程的有效用户ID.(1)新文件的组ID可以是进程的有效组ID(2)新文件的组ID可以是它所在目录的组ID

4.7 函数access和faccessat

1、access和faccessat函数是按实际用户ID和实际组ID进行访问权限测试的。

4.8 函数umask

1、umask函数为进程设置文件模式创建屏蔽字,并返回之前的值

2、在文件模式创建屏蔽字时,cmask参数要被屏蔽的权限

mode_t umask(mode_t cmask); //cmask代表的栏位就是需要关闭的权限。

3、更改进程的文件模式创建屏蔽字并不影响其父进程的屏蔽字

4、用户可以设置umask值以控制他们所创建文件的默认权限。

4.9 函数chmod、fchmod和fchmodat

1、这三个函数改变现有文件的访问权限。

2、chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。

3、chmod函数更新的只是i节点最近一次被更改的时间。按系统默认方式,ls -al列出的是最后修改文件内容的时间。

4.10 粘着位

1、如果可执行程序文件设置粘着位,当该程序第一次被执行时,在其终止时,程序正文部分的一个副本仍被保存在交换区(程序的正文部分时机器指令),这使得下次执行该程序时能较快地将其装载入内存。

2、如果对一个目录设置粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:(1)拥有此文件(2)拥有此目录(3)是超级用户。

4.11 函数chown、fchown、fchownat和lchown

1、用于更改文件用户ID和组ID,改变文件的所有者

4.12 文件长度

1、stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义

2、对于普通文件,其文件长度可以是0;对于目录,文件长度通常是一个数的整倍数;对于符号链接,文件长度是在文件名中的实际字节数。

3、空洞是设置的偏移量超过文件尾端,并写入了某些数据后造成的。

4.13 文件截断

1、函数truncate和ftruncate可以将一个现有文件长度截断为length.

2、不会改变截断文件的时间

4.14 文件系统

1、i节点是固定长度的记录项,它包含有关文件的大部分信息。

2、只有当i节点链接计数减少至0时,才可删除该文件(也就是可以释放该文件占用的数据块)

3、符号链接文件的实际内容包含了该符号链接所指向的文件的名字。

4、i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等

5、在父目录中的每一个子目录都使改父目录的连接技术增加1

4.15 函数link、linkat、unlink、unlink和remove

1、创建一个指向现有文件的链接的方法是使用link函数或linkat函数

2、unlink为删除一个现有的目录项,被程序用来确保即使是在程序崩溃时,它所创建的临时文件也不会遗留下来

3、unlike删除该符号链接,而不是删除由该链接所引用的文件。

4、remove函数解除一个文件或目录的链接。

4.16 函数rename和renameat

1、文件或目录可以用rename函数或者renameat函数进行重命名。

4.17 符号链接

1、符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。

2、硬链接的限制:硬链接通常要求链接和文件位于同一文件系统中;只有超级用户才能创建指向目录的硬链接。

3、符号链接一般用户将文件或整个目录结构移到系统中的另一个位置

4、link函数不允许构造函数指向目录的硬链接。

5、如果传递给open函数的路径名指定了一个符号链接,那么open跟随此链接到达所指定的文件。

4.18 创建和读取符号链接

1、可以用symlink或symlinkat函数创建一个符号链接。链接和被链接的文件不需要位于同一系统中

2、readlink和readlinkat可以打开该链接本身,并读该链接中的名字。

4.19 文件的时间

1、每个文件属性所保存的实际进度依赖于文件系统的实现。

2、每个文件维护3个字段:st_atim表示文件数据的最后访问,st_mtim表示文件数据的最后修改时间,st_ctim表示i节点状态的最后修改时间

3、i节点中的所有信息都是与文件的实际内容分开存放的。

4、系统并不维护对一个i节点的最后一次访问时间,access和stat函数并不更改3个时间中任一个

5、ls中是按文件修改时间的先后排序显示。-u选项是ls命令按访问时间排序,-c选项则使其按状态更改时间排序。

4.20 函数futimens、utimensat和utimes

1、一个文件的访问和修改时间可以用以上几个函数进行修改。

2、我们不能对状态更改时间st_ctime(i节点最近被修改的时间)指定一个指,调用相关函数后,会自动更新文件状态时间为最新时间

4.21 函数mkdir、mkdirat、rmdir

1、指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。

2、为了使rmdir函数成功执行,该目录必须是空的。

3、前俩个函数创建目录,后一个函数删除目录

4.22 读目录

1、对某个目录具有访问权限的任一用户都可以读该目录,但是为防止文件系统产生混乱,只有内核才能写目录。

2、fdopendir函数可以把打开文件描述转换成目录处理函数需要的DIR结构

4.23 函数chdir、fchdir和getcwd

1、进程调用chdir或fchdir函数可以更改当前工作目录

2、getcwd获取当前进程运行的工作目录完整的绝对路径名

4.24 设备特殊文件

1、只有字符特殊文件和块特殊文件才有st_rdev值

第五章 标准I/O库

5.1 引言

1、标注I/O库,不仅是UNIX,很多其他操作系统都实现了标准I/O库

2、标准I/O库处理很多细节,如缓冲区分配、以优化块长度执行I/O等。

5.2 流和FILE对象

1、对于标准I/O 库,他们的操作围绕流进行的。当标准I/O库打开或创建一个文件时,我们已经使用一个流与文件相关连。

2、流的定向决定了所读、写的字符是单字节(字节定向)还是多字节(宽字节定向)。

3、freopen函数清楚一个流的定向,fwide函数可用于设置流的定向

4、当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构体,它包含了标准I/O库为管理流需要的所有信息,包括用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区长度、当前在缓冲区中的字符数以及出错标志等。

5、为了引用一个流,需要将FILE指针作为参数传递给每个标准I/O函数。

5.3 标准输入、标准输出和标准错误

1、对一个进程预定义了3个流,并且这3个流可以自动地被进程使用,他们是标准输入、标准输出和标准错误。

1、标准I/O流通过预定义文件指针stdin、stdout和stderr加以引用

每次一个字符I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。

5.4 缓冲

1、提供缓冲的问题是尽可能减少使用read和write调用次数。

2、标准I/O提供三种类型的缓冲:(1)全缓冲,在填满标准I/O缓冲区后才进行实际I/O操作。(2)行缓冲,当在输入和输出遇到换行符时,标准I/O库执行I/O操作。(3)不带缓冲,例如fput函数

3、更改缓冲类型setbuf和setvbuf。setbuf函数打开或关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区。

4、可以使用fflus强制冲洗一个流

5.5 打开流

1、打开一个标准I/O流。(1)fopen函数打开路径名为pathname的一个指定文件。(2)freopen函数在一个指定的流上打开一个指定的文件。如若该流已经打开,则先关闭流。若该流已经定向,则使用freopen清楚该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。(3)fdopen函数取一个已有的文件描述符,并使一个标准I/O流与该描述符相结合。此函数常用于创建管道和网络通信通道函数返回的描述符。

2、fdopen为写而打开并不截断该文件。

3、如果有多个进程用标准I/O追加写方式打开同一文件,那么来自每个进程的数据都将正确地写到文件。

4、以读和写类型打开一个文件时,具有下列限制(1)如果中间灭哦与fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。(2)如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。

5、除非流引用终端设备,否则按系统默认,流被打开时是全缓冲

6、当一个进程正常终止时,所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭

7、fclose关闭一个打开的流。

5.6 读和写流

1、一旦打开了流,则可在3中不同类型的非格式化I/O中进行选择,对其读、写操作

(1)每次一个字符I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。

(2)每次一行I/O。如果想要一次读或写一行,则使用fgets和fputs。

(3)直接I/O。fread和发write函数支持这种类型I/O。酶促I/O操作读或写某种数量的对象,而每个对象具有指定的长度。

2、输入函数getc\fgetc\getchar可用于一次读一个字符。其中getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点:

(1)getc的参数不应当具有副作用的表达式,因为可能会被计算多次。

(2)fgetc一定是一个函数,所以可以得到其地址。这将允许将fgetc的地址操作传送给另一个函数

(3)调用fgetc所需时间很可能比调用getc要长

3、不管出错还是到达文件尾端,这3个函数返回同样的值。为了区分这俩种不同的情况,可以调用ferror或feof确定。每个流在FILE对象中维护俩个标志位:出错标志、文件结束标志,调用clearerr可以清除这俩个标志。

4、从流程读取数据后,可以调用ungetc将字符再压送回流中,只是将他们写回标准I/O库的流缓冲区中

5、输出函数putc\fputc\putchar对应每个输入函数。

5.7 每次一行I/O

1、gets从标准输入读,而fgets则从指定的流读

2、fgets必须指定缓冲区的长度n。此函数一直读到下一个换行符位置,但不超过n-1个字符,读入的字符被送入缓冲区。因为gets不能指定缓冲区,存在缓冲区溢出风险,不推荐使用。gets并不将换行符存入缓冲区中。

3、fputs和puts提供每次输出一行的功能,fputs将一个以null字节终止的字符串写到指定的流,尾端终止符null不写出。

4、总是使用fgets和fputs,那么在每行终止处我们需要自己处理换行符。

5.8 标准I/O的效率

1、exit函数会冲洗任何未写的数据,然后关闭所有打开的流。

5.9 二进制I/O

1、进行二进制I/O操作,可以进行一次读或写一个完整的结构,函数:

size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);size_t fwrite(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);

 2、使用二进制I/O的基本问题是,只能用于读在同一系统上已写的数据。因为在一个结构中,同一成员的偏移量可能随编译程序和系统的不同而不同。

3、在不同系统之间交换使用互认的规范格式。

5.10 定位流

1、有三种方法定位I/O流(1)ftell和fseek函数(2)ftello和fseek函数(3)fgetpos和fsetpos函数,移植到非UNIX系统上,应当使用此函数。

2、对于二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位

5.11 格式化I/O

1、格式化输出函数

int printf(const char *restrict format,.....);//将格式化数据写到标准输出int fprintf(FILE *restrict fp, const char *restrict format,....);//写至指定的流int dprintf(int fd, const char *restrict format,......);//写至指定的文件描述符int sprintf(char *restrict buf, const char *restrict format, ......);//将格式化的字符送入数组buf中,此函数可能会导致缓冲区溢出

2、格式化输入函数

int scanf(const char *restrict format,....);int fscanf(FILE *restrict fp,const char *restrict format,....);int sscanf(const char *restrict bug, cont char *restrict format,...);

5.12 实现细节

1、每个标准I/O流都有一个与其相关量的文件描述符,可以对一个流调用fileno函数获得其描述符。

5.13 临时文件

1、标准I/O库提供了俩个函数以帮忙创建临时文件

char *tmpnam(char *ptr);FILE *tmpfile(void);//在关闭该文件或程序结束时将自动删除这种文件。char *mkdtemp(char *template);int mkstemp(char *template);//以唯一的名字创建一个普通文件并且打开该文件

5.14 内存流

1、所有I/O都是通过缓冲区与主存之间来回传送字节来完成的。

2、用于创建内存流的3个函数

FILE *fmemopen(void *restrict buf, size_t size,const char *restrict type);//允许调用者提供缓冲区用于内存流:buf参数指向缓冲区开始的地方,//size参数指定了缓冲区大小的字节数。fseek函数会引起缓冲区冲洗FILE *open_memstream(char **bufp, size_t *sizep);//创建的流是面向字节的FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);//创建的流是面向宽字节的

5.15 标准I/O的替代软件

标准I/O库的一个不足之处是效率不高,这与它需要复制的数据量有关。例如使用fgets和fputs,需要复制俩次数据:一次是在内核和标准I/O缓冲区之间,第二次是在标准I/O缓冲区和用户程序中的行缓冲区之间。

 第6章 系统数据文件和信息

6.1 口令文件

1、通常一个用户名为root的登录项,其用户ID是0(超级用户)

2、关于口令文件相关的函数

struct passwd *getpwuid(uid_t uid);struct passwd *getpwnam(const char *name);//返回一个指向passwd结构的指针只查看登录名或用户IDstruct passwd *getpwent(void);void setpwent(void); void endpwent(void);

6.3 阴影指令

1、某些系统将加密口存放在另一个通常称为影音口令的文件中

 2、访问阴影口令文件函数

struct spwd *getspnam(const char *name);struct spwd *getspent(void);void setspent(void);void endspent(void);

6.4 组文件

1、查看组名或数值组ID函数

struct group *getgrgid(gid_t gid);struct group *getgrname(const char *name);

 2、搜索整个组文件函数

struct group *getgrent(void);void setgrent(void);void endgrent(void);

6.2 附属组ID

1、可以在任何时候执行newgrp以更改组ID.

2、获取和设置附属组ID相关函数

int getgroups(int gidsetsize, gid_t_grouplist[]);int setgroups(int ngroups, const gid_t grouplist[]);int initgroups(const char *username, gid_t hasegid);

6.9 系统标识

1、uname函数返回与主机和操作系统有关的信息

2、gethostname函数,只返回主机名,该名字通常就是TCP/IP网络上的主机名字。

6.10 时间和日期例程

1、time函数返回当前时间和日期

time_t time(time_t *calptr);

2、clock_gettime函数可以用于获取指定时钟的时间。

3、对特定时钟设置时间,可以调用clock_settime函数

3、locltime和gmtime之间的区别是:localtime将日历时间转换成本地时间

4、函数mktime以本地时间的年、月、日等作为参数,将其变换成time_t值。

5、函数strftime是一个类似于printf的时间值函数。

7 进程环境

7.2 main函数

1、C程序总是从main函数开始执行。

int main(int argc,char *argv[]);

7.3 进程终止

1、有8中方式使进程终止,其中5种为正常终止,他们是:

(1)从main返回

(2)调用exit

(3)调用_exit或Exit

(4)最后一个线程从其启动例程返回

(5)从最后一个线程调用pthread_exit

异常终止有3种方式,他们是:

(6)调用abort

(7)接到一个信号

(8)最后一个线程对取消请求做出响应

2、退出函数,3个函数用于终止一个程序:_exit和_Exit立即进入内核,exit则执行一个标准I/O库的清理关闭操作,然后返回内核。三个函数都带一个整型参数,称为终止状态。

3、atexit函数可以用来登记终止处理程序。atexit的参数是一个函数地址,当调用此函数时无需向它传递任何参数。先被登记的函数后调用

 7.4 命令行参数

1、当执行一个程序是,调用exec的进程可将命令行参数传递给该新程序。

7.5 环境表

1、每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串地址。

2、environ为环境指针,指针数组为环境表,其中各指针指向字符的字符串为环境字符串。

3、通常用getenv和putenv函数来访问特定的环境变量

7.6 C程序的储存空间布局

1、C程序由夏磊几部份组成:(1)正文段,是由CPU执行的机器指令部分(2)初始化数据段,它包含了程序中需明确地赋初值的变量(3)未初始化数据段(4)栈,自动变量以及每次函数调用时所需保存的信息都存放在此段中(5)堆,通常在堆中进行动态存储分配。

 2、未初始化的数据段内容并不存放在磁盘程序文件中。需要存放在磁盘程序文件中的段只有正文段和初始化数据段

7.7 共享库

1、共享库使得可执行文件中不再需要包含公用的库函数,只需要在所有进程都可引用的储存区中保存这种库例程的一个副本。

2、共享库减少了每个可执行文件的长度,但增加了一些运行时间开销。这种开销发生在该程序第一次被执行时,或者每个共享库第一次被调用时。用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑

7.8 储存空间分配

1、3个用于储存空间动态分配的函数

(1)malloc,分配指定字节数的储存区。此储存区中的初始值不确定

(2)calloc,为指定数量指定长度的对象分配存储空间。该空间中每一位都初始化为0。

(3)realloc,增加或减少以前分配区的长度。新增区域内的初始值不确定

void *malloc(size_t size);void *calloc(size_t nobj,size_t size);void *realloc(void *ptr, size_t newsize);//新增长度后,可能导致原来指向的位置被释放,使用新的储存空间
void free(void *ptr);

2、未声明函数的默认返回值为int。

4、避免内存泄漏,,提供替代的储存空间分配程序(1)libmalloc、vmalloc、quick-fit、jemalloc、TCMalloc、函数alloca在栈帧上分配存储空间。

 7.9 环境变量

1、函数getenv可以获取环境变量

2、设置环境变量函数

(1)putenc取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原来的定义

(2)setenv将name设置为value,必须分配存储空间

(3)unsetenv删除name的定义。

7.10 函数setjmp和longjmp

1、goto语句是不能跨越函数的,而执行这种类型跳转功能的是函数setjmp和longjmp。

2、一个setjmp可以对应多个longjmp

int setjmp(jmp_bug env);//若直接调用返回0,若从longjmp返回,返回longjmp设置的valvoid longjmp(jmp_buf env, int val);//设置的val值,会作为setjmp函数的返回值,用来确定从longjmp返回的

3、回滚到setjmp函数后,自动变量和寄存器变量的值不回滚,而所有标准则称他们值不确定,如果有自动变量,而又不想其指回滚,则可定义其为具有volatile属性。声明为全局变量或静态变量的值在执行longjmp时保持不变。

4、基本规则是声明自动变量的函数已经返回后,不能引用这些 自动变量

7.11 函数getrlimit和setrlimit

1、每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit函数查询和更改。

int getrlimit(int resource, struct rlimit *rlptr);int setrlimit(int resource, const struct rlimit *rlptr);

2、更改资源限制时,必须遵循下列3条规则:(1)任何一个进程都可将一个软限制更改为小于或等于其硬限值(2)任何一个进程都可降低其硬险值,但它必须大于或等于其软限值。这种降低,对普通用户而言是不可逆的(3)只有超级用户进程可以提高硬限值。

3、资源限制影响到调用进程并由子进程集成。

4、字符串创建算符(#)

#define doit(name) pr_limits(#name,name)doit(RLIMIT_CORE)扩展为 pr_limits(“RLIMIT_CORE”,RLIMIT_CORE);

8 进程控制

8.1 引言

1、进程控制包括创建新进程、执行程序和进程终止

8.2 进程标识

1、每个进程都有一个非负整型表示的唯一进程ID,但是进程ID可以复用,当一个进程终止后,其进程ID就成为复用的候选者。

2、ID为0的进程通常是调度进程,常被成为交换进程。该进程是内核的一部分,它并不执行任何磁盘上的程序,因此也被称为系统进程。

3、进程ID 1通常是init进程,在自举过程结束时由内核调用。init通常读取与系统有关的初始化文件,并将系统引导到一个状态。init进程决不会终止。进程ID 2是页守护进程,此进程负责支持虚拟储存器系统的分页操作。

4、除了进程ID,每个进程还有一些其他标识符。下列函数返回这些标识符。

pid_t getpid(void);返回调用进程的进程IDpid_t getppid(void);返回调用进程的父进程IDpid_t getuid(void);返回调用进程的实际用户IDpid_t geteuid(void);返回调用进程的有效用户IDpid_t getgid(void);返回调用进程的实际组IDpid_t getegid(void);返回调用进程的有效组ID

8.3 函数fork

1、一个现有的进程可以调用fork函数创建一个新进程,调用一次,有俩次返回

pid_t fork(void);//子进程返回0,父进程返回子进程的ID

2、子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如子进程获得父进程数据空间、堆和栈的副本。父进程和子进程并不共享储存空间部分。父进程和子进程共享正文段。子进程对变量修改,并不影响父进程。会将父进程的地址空间复制一份到子进程,所以父子进程中,变量互不干扰(调用exec前)

3、注意在fork前调用printf("*\n")(标准I/O函数库),如果是标准输出只打印一次,因为标椎输出缓冲区是由换行符重洗。但是当标准输出重定向到文件时,printf会打印俩次。

4、计算字符串是,strlen不包含终止null字节的字符串长度,而sizeof则计算包含终止null字节的缓冲区长度。使用strlen需进行一次函数调用,而对于sizeof而言,因为缓冲区已用已知字符串进行初始化,其长度是固定的,所以sizeof是在编译时计算缓冲区长度。

5、fork的一个特性是父进程的所有打开文件描述都被复制到子进程中,类似执行了dup函数。父进程和子进程每个相同的打开描述符共享一个文件表项。父进程和子进程共享一个文件偏移量

4、运用套接字函数,可以命名UNIX域套接字。

17.3 唯一连接

1、服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程联系。

2、在运行于同一台计算机上的俩个无关进程之间创建唯一连接的函数

int serv_listen(const char *name);//声明它要在一个众所周知的名字(文件系统中的某个路径名)上监听客户进程的连接请求。函数返回值用于接收客户进程连接请求的服务器UNIX域套接字。int serv_accept(int listenfd, uid_t *uidptr);//等待客户进程连接请求的到达。int cli_conn(const char *name);//连接至服务器进程。

17.4 传送文件描述符

1、传送文件描述符是指一个进程向另一个进程传送一个打开文件描述符时,可以让发送进程和接收进程共享同一文件表项。

2、用以发送和接收文件描述符的3个函数

int send_fd(int fd, int fd_to_send);int send_err(int fd, int status, const char *errmsg);//以上俩个函数是将一个描述符传送给另一个进程int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t));//客户进程接收描述符函数

3、用于访问控制数据的宏

unsigned char *CMSG_DATA(struct cmsghdr *cp);struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);unsigned int CMSG_LEN(unsigned int nbytes);

 17.5 open服务器进程第1版

end

待梳理

1、在P86中(statbuf.st_mode & ~S_IXGRP) | S_ISGID)<0逻辑待数理

1、execlp函数执行逻辑,为什么不能执行 ls -al的逻辑

待进一步梳理知识点:
1、select和poll函数使用实例
2、popen和pclose函数的具体使用场景

3、过滤程序概念

4、关于getaddrinfo函数使用待确定

5、set_cloexec函数的作用

6、daemonzie函数用法待确认

7、16-6和16-17无法运行起来

8、16-9无法运行起来

9、出现getaddrinfo error: Servname not supported for ai_socktype

end

需要做网站?需要网络推广?欢迎咨询客户经理 13272073477