UNIX 环境高级编程之我见

最后更新于:2022-04-01 20:03:05

**《UNIX环境高级编程》(第二版)(人民邮电出版社)** **【美】W.Richard Stevens& Stephen A.Rago 著** 本书的主要结构分为以下几个部分: (1).**第1章UNIX基础知识** ①UNIX体系结构中,最主要的是内核,它有一些称为系统调用的接口与外界交互。在内核之上有shell 和库函数,然后是应用软件。 ②常见的shell有Bourneshell(sh), Bourne_again shell(bash), C shell(csh), Korn shell(ksh), TENEX Cshell(tcsh)。 ③不能出现在文件名中的字符只有斜线(/)和空操作符(null)两个。 ④文件描述符(file descriptor)通常是一个小的非负整数。 ⑤当一个进程收到一个信号时,有三种选择:忽略该信号;按系统默认方式处理;提供一个函数,信号发生时调用这个函数。 **(2) 第1章UNIX标准及其实现** 1、本章介绍了三个主要标准:ISO C、POSIX和SingleUNIX Specification 。POSIX标准中的都只是接口,而不是实现,所以不区分系统调用和库函数,都称为函数。Single UNIX Specification(单一UNIX规范)是POSIX.1标准的一个超集,定义了一些附加的接口。 2、提高移植性的限制有两类:编译时限制和运行时限制。ISO C定义的限制都是编译时限制,列在头文件中。POSIX.1定义的限制和常量有5类:①不变的最小值;②不变值;③运行时可以增加的值;运行时不变的值(可能不确定);⑤路径名可变值(可能不确定)。 3、如果在编译一个程序时,希望它只使用POSIX的定义而不使用任何其他的定义,就需要定义常量_POSIX_C_SOURCE。 **(3)文件I/O** 1、UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close。由于新的open函数提供了O_CREAT和O_TRUNC选项,也就可以完全替代creat函数了。Lseek函数的作用就是为一个打开的文件设置其当前文件偏移量,定位读写的位置。 2、内核使用三种数据结构表示打开的文件:进程表中的一个记录项、文件表、v节点(在Linux中由通用型的i节点来实现)。 3、使用pread和pwrite函数可以实现原子读写。Dup和dup2函数可以复制一个现存的文件描述符。Sync、fsync和fdatasync函数可以使放在缓存中的数据写入磁盘,免得系统崩溃时造成数据丢失。Fcntl函数可以改变已打开文件的性质。 **(4)文件和目录** 1、本章讨论的中心是3个stat函数以及它们返回的信息。Stat函数返回文件的信息结构,fstat函数获取描述符为filedes的文件的有关信息,lstat可以返回符号链接的有关信息而不是它所指向的文件的有关信息。 2、其他函数:access函数按实际用户ID和实际组ID进行访问权限测试;umask函数为进程设置文件模式创建屏蔽字;chmod和fchmod函数用于更改现有文件的访问权限;chown、fchown和lchown函数用于更改文件的用户ID和组ID;truncate和ftruncate函数把现有文件截短为参数length字节;link、unlink创建和删除一个指向现有文件的链接(硬链接);symlink函数创建一个符号链接;utime函数可以更改一个文件的访问和修改时间;chdir、fchdir函数可以更改当前工作目录;getcwd返回工作目录的绝对路径。 **(5) 标准I/O库** 1、在UNIX系统中,标准I/O库最终都要调用第3章中说明的I/O例程。 2、当用标准I/O库打开或创建一个一个文件时,我们已使一个流与一个文件相关联。每个标准I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数以获取其描述符。 3、对一个进程预定义了3个流:标准输入、标准输出和标准出错。 4、打开标准I/O流的函数:fopen、freopen、fdopen。用fclose函数关闭。 5、流的读写函数:getc、putc,fgetc、fputc,getchar、putchar,fgets、fputs,gets、puts(这一对不推荐使用)。 6、格式化输入输出函数:printf、scanf,fprintf、fscanf,sprintf、snprintf、sscanf。 7、使用tmpnam和tmpfile函数可以创建临时文件。 **(6 )系统数据文件和信息** 1、一般情况下,对于每个数据文件至少有三个函数:get函数,读出记录;set函数,打开文件然后反绕它;end函数,关闭文件。 2、与用户ID相关的几个重要的文件是:口令文件(passwd);阴影口令文件(shadow);组文件(group);utmp文件,记录当前登录进系统的各个用户;wtmp文件,跟踪各个登录和注销事件。 3、关于时间和日期的一个函数。Time函数返回当前时间:自1970年1月1日零时以来的秒数,而且是国际标准时间。Gettimeofday函数与time类似,只是可以提供更高的分辨率(微秒)。Localtime和gmtime函数将日历时间转换成以年、月、日、时、分、秒周日表示的时间,填充structtm结构。而mktime函数则相反,转换成time_t值,也就是日历时间。Asctime和ctime函数产生26字节的字符串,表示年月日时分秒等信息。Strftime函数提供格式化的时间表示,类似于printf[**程环境**��f-�L�Ntuation']() [**]()** **(7 ) 进程环境** 1、进程的正常终止大多是调用exit函数,它先调用各终止处理程序(由atexit函数登记),然后按需多次调用fclose关闭打开的所有流。 2、一个C程序的组成部分:正文段(指令)、初始化数据段、非初始化数据段(bss)、栈(函数调用时使用)、堆(动态存储分配)。 3、存储空间的动态分配函数有3个:malloc、calloc、realloc。而free函数释放存储空间。 4、系统中的环境变量由各个应用程序解释使用,内核不过问。相关的3个函数,putenv添加一个环境变量、setenv更改一个环境变量、unsetenv删除一个环境变量。 5、在C中,goto语句是不能跨越函数的,而执行这类跳转功能的是函数setjmp和longjmp 。 6、使用getrlimit和setrlimit函数查询和修改系统对一个进程的资源限制。 **(8 ) 进程控制** 1、每一个进程都有一个非负整型表示的唯一进程ID。这是最重要的一个进程标识符。 2、本章的重点fork。一个现有进程可以调用fork函数创建一个新进程。有两种用法:一个父进程希望复制自己,使父、子进程同时执行不同的代码段;一个进程想启动一个完全不一样的进程(例如利用shell执行命令)。Fork调用一次返回两次,在子进程中返回0,在父进程中返回子进程的ID。 3、vfork用于创建一个新进程,但其目的是exec一个新程序。就像fork的第二种用法。但是vfork保证子进程先运行。而对于fork,子进程还是父进程先运行是不确定的。 4、对于一个进程的任意一种终止情形,我们都希望被终止的进程能够通知其父进程它是如何终止的。终止函数exit、_exit和_Exit把进程的退出状态作为参数传递给函数。 5、调用wait和waitpid函数可以等待子进程终止的异步信号的通知。但是一个子进程终止之前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。 6、exec函数族。调用exec函数不创建新的进程,前后进程ID并不改变,它只是用一个全新的程序替换了当前进程的正文、数据、堆和栈。一共有6个函数:execl、execv、execle、execve、execlp和execvp。Exec函数的一种变体:解释器文件。Shell脚本是其中比较常见的一种。 **(9) 进程关系** 1、本章最重要的是三个概念:进程组、会话和作业控制。进程组是一个或多个进程的集合,拥有一个唯一的ID,有一个组长进程。进程可以通过调用setpgid来加入一个现有的组或者创建一个新进程组。而会话是一个或多个进程组的集合,进程调用setsid函数建立一个新会话。作业控制允许在一个终端上启动多个作业(进程组),它控制哪一个作业可以访问终端,哪些作业在后台运行。 **(10)信号** 1、这一章是相当重要的一章,用了50多页来讲解。含有大量的实例程序,讲解得比较深入。 2、信号是软件中断,用于处理异步事件。每一个信号都有一个名字,而且都以三个字符SIG开头,对应一个整型编号。信号产生的情况:①用户按下某些终端按键;②硬件异常;③调用kill函数发送;④用户使用kill命令发送;⑤检测到某种软件条件已经发生。三种信号处理方法:①忽略它;②捕捉它,然后调用特定的用户函数;③执行系统默认动作。 3、一些与信号有关的重要的函数。Signal函数用于设置某信号的处理程序,在很多平台该函数都是由sigaction函数来实现的。Kill函数将信号发送给进程或进程组。Alarm函数设置过一段时间后发送信号给自己,而pause函数使自己挂起直到捕捉到一个信号。Sigprocmask函数可以检测或更改其信号屏蔽字。Sigpending函数返回对应进程是阻塞的信号集。Sigsuspend函数解除一个阻塞信号并且马上使进程休眠,两步是一个原子操作。Abort函数用于使异常程序终止,进程接收到信号后终止之前可以执行一些必须的清理工作。 **(11)线程** 1、这一章有三部分的内容,线程的概念、线程的创建和终止、线程的同步。 2、多线程设计的好处:①使得处理异步事件的代码变得简化。②共享方便。③提高整个程序的吞吐量。④改善交互程序的响应时间。 3、线程包含了表示进程内执行环境必需的信息,包括线程ID(只在它所属的进程环境中有效)、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。 4、线程的创建使用pthread_create函数。但是创建后不保证哪一个线程先运行。如果进程中的任一线程调用了exit、_Exit或者_exit,那么整个进程就会终止。而单个线程有三种方式推出:①从启动例程中返回。②被同一进程中的其他线程取消。③自己调用pthread_exit函数。 5、实现线程同步主要有三种方式:使用互斥量、使用读写锁、使用条件变量。 **(12) 线程控制** 1、线程属性存放于数据结构pthread_attr_r中。可以使用函数pthread_attr_init函数来初始化这个数据结构,使用pthread_attr_destroy函数来用无效数值填充,即“反初始化”。使用函数pthread_attr_setdetachstate函数修改结构中的detachstate属性,可以让线程以分离状态启动。线程的栈属性通过pthread_attr_setstack函数来设置。 2、线程的同步属性包括互斥量属性、读写锁属性、条件变量属性。它们都有对应的进出共享属性,存在于不同的数据结构中。 3、如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。如果函数对异步信号处理程序的重入是安全的,那么就说函数是异步-信号安全的。 4、另外是线程属性还有可取消状态和可取消类型。这两个属性影响着线程在响应pthread_cancle函数调用时所呈现的行为。 **(13)守护进程** 1、守护进程也称精灵进程(daemon)。 2、守护进程编程规则:①调用umask将文件模式创建屏蔽字设置为0。②调用fork,然后使父进程退出。③调用setsid以创建一个新会话。④将当前工作目录更改为根目录。⑤关闭不再需要的文件描述符。 3、守护进程没有控制终端,它的出错信息通过syslog设施来记录。对应的函数有openlog、syslog、closelog、setlogmask。 **(14) 高级I/O** 1、非阻塞I/O使得当我们使用open、read、write这样的操作时,如果不能完成则立即出错返回,不会阻塞。 2、记录锁对一个文件区域进行加锁,当一个进程正在对一个文件的某区域进行操作时可以阻止被另外一个进程对其的操作而引起的混乱。注意,这不是对整个文件加锁,只是一个文件的指定区域,所以这个锁也叫字节范围锁。 3、当一个进程需要请求多个描述符时,可以使用I/O多路转接技术来处理。大致是先构造一张有关描述符的表,然后调用一个函数,直到这些描述符中的一个准备好进行I/O时,该函数才返回。可以选择的函数有三个:poll、pselect和select。 4、高级I/O还提供了一些很有用的扩展函数。Readv和writev函数实现在一次函数调用中读、写多个非连续区域。Readn和writen函数可以指定读、写N个字节的数据,能够自动处理返回值小于要求值的情况,是多次调用read和write函数实现的。 **(15) 进程间通信** 1、最古老的IPC是管道,它有两方面的局限性:半双工传输(有个别系统实现了全双工);只能在有公共祖先的进程间使用。一些相关函数:pipe函数用于创建一个新管道,popen函数实现创建一个管道然后调用fork产生一个子进程,然后使用管道与其通信。 2、当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就称为协同进程。 3、FIFO有时被称为命名管道。一些相关函数:mkfifo用于创建,使用open函数打开。 4、有三种IPC统称为XSI IPC,分别是消息队列、信号量(其实它真正上是一种同步原语)、共享存储器。每一个XSI IPC结构都有对应的一个非负整型标识符和一个键。三种的创建函数分别为msgget、semget、shmget。 **(16 )网络IPC:套接字** 1、套接字是通信端点的抽象,访问套接字要用套接字描述符。创建一个套接字使用socket函数。使用函数shutdown可以禁用一个套接字,通过参数决定关闭写端或者读端。 2、套接字使用的地址有一个通用的结构:socketaddr结构。使用函数bind可以将地址绑定到一个套接字,而调用函数getsocketname查看绑定到一个套接字的地址。 3、使用函数connect建立一个连接。对于服务器,可以调用listen函数来宣告可以接受连接请求,然后使用函数accept函数获得连接请求并建立连接。 4、六个数据传送相关的函数。Send和sendto函数很相似,都是把数据发出去,只是sendto允许在无连接的套接字上指定一个目标地址。而,sendmsg函数可以指定多重缓冲区传输数据,类似于writev函数。Recv函数用来接收数据,recvfrom函数比recv多一个功能是可以得到数据发送者的地址。对应于sendmsg,有recvmsg函数。 **(17 ) 高级进程间通信** 1、基于STREAMS的管道(简称STRREAMS管道,STREAMS pipe)是一个双向(全双工)管道。可以用fattach函数给STREAMS管道一个文件系统中的名字,使用fdetach函数撤销它。 2、UNIX域套接字用于在同一台机器上运行的进程之间的通信。提供流和数据报两种接口。使用socketpair函数可以创建一对非命名的、相互连接的UNIX域套接字。 **(18) 终端I/O** 1、终端设备是由位于内核中的终端驱动程序控制的,都有一个输入队列和输出队列。终端设备的所有特性都包含在termios结构中,该结构有四大标志:c_cflag, c_lflag,c_iflag, c_oflag。使用函数tcgetattr和tcsetattr可以获得和设置termios结构,而在命令行中可以使用stty命令。 2、终端I/O有两种不同的工作模式:规范模式输入处理(以行为单位处理)和非规范模式输入处理(不以行为单位处理输入数据)。 **(19) 伪终端** 1、伪终端这个术语暗示对于一个应用程序而言,它看上去像一个终端,但实际上应用程序被欺骗了。从内核角度看,伪终端看起来像一个双向管道。而事实上Solaris的伪终端就是用STREAMS构建的。 2、一些相关的调用函数。Posix_openpt函数用来打开下一个可用的伪终端主设备。用于更改权限的两个函数是grantpt和unlockpt。确定路径名用ptsname函数。 3、当我们用pty来执行另外一个程序时,该程序在一个它自己的会话中执行,并和一个伪终端连接。具体对伪终端的应用主要有以下几个。Utmp文件、作业控制交互、检查长时间运行程序的输出、script程序、运行协同进程、用非交互模式驱动交互式程序。 **(20) 数据库函数库** 1、本章详细介绍了一个数据库函数库的设计与实现。此函数库包括的一些函数有:数据库的打开与关闭函数:db_open, db_close;存储和删除一条记录:db_store,db_delete;从数据库获取一条记录:*db_fetch;访问数据库所有记录的两个函数:db_rewind, *db_nextrec。 2、数据库被建立时创建两个文件:索引文件和数据文件。索引文件的数据通常使用散列法或B+树来组织,从而提高数据的访问速度。对于进程对数据库数据的访问,有集中式和非集中式两种实现方法。集中式是指由一个数据库进程作为数据库管理者,所有的数据库访问工作由此进程完成,其它进程通过IPC与此中心进程联系。非集中式是指每个库函数独立申请并发控制,然后自己调用I/O函数。 **(21)与网络打印机通信** 1、本章实现了两个程序:一个打印假脱机守护进程,用以将作业发送到打印机;一个命令行程序,用以将打印作业提交到假脱机守护进程。与网络打印机通信使用的是网络打印协议(IPP),而它建立在HTTP和TCP/IP的基础之上。 总结:这是一本经典的书,对于unix 程序员来说,本书一直遵循三个标准来进行编写。 (1). iso c ,ieee posix ,The single unix specification 这三个标准一直贯穿始终。 (2). 在某些方面还提到了**limit **这个概念,对于可移植性来说非常重要。 (3). 区分了标准io 和文件io 及其区别,及其文件描述符的重要性。 (4). 还有高级io 的4种方式(阻塞,非阻塞,select ,异步驱动模型)等好多有用的知识。 (5). linux中 七种的通信方式,无名管道,有名管道,信号,ipc(共享内存,消息队列,信号灯),还有 socket ,分别由unix ,贝尔实验室,伯克利分校实验室 继承而来的几套标准。 (6) . 讨论的伪终端 更是非常的透彻,主设备,从设备的关系,以及伪设备运行在从设备上的原理和用处, (7). 本书还深刻讲解了 进程,线程 的一些同步机制,和互斥锁,类似pv 操作的东西。 (8). 本书给我最大的感受就是,纠正了好多的编程的习惯,错误的认识,提高了我的编程的视角和高度,全面的从系统出发,从全局出发,如 其linux pam 机制([http://www.chinaunix.net/old_jh/4/390136.html](http://www.chinaunix.net/old_jh/4/390136.html))查阅相关资料。
';