UNIX环境高级编程APUE- 美 - 斯蒂文斯 - 人民邮电出版社
POSIX官网
POSIX是所有unix-like系统都需要遵循的规范,其定义了操作系统需要提供的接口,官网在 (https://pubs.opengroup.org/onlinepubs/9699919799/),其中的System Interfaces节点有操作系统接口列表。
前言
本书描述了UNIX系统的程序设计接口,包括系统调用和标准C库。
UNIX基础知识
UNIX体系结构下图,内核的接口称为系统调用,公共函数库构架在系统调用之上,应用程序既可使用公共函数库,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
文件名只有斜线(/)和空字符不能出现在文件名中,斜线是用来分隔路径的,空字符是用来终止一个文件名的。尽管如此,如果文件名中使用了shell的特殊字符,则必须使用shell的引号机制来引用文件名。
UNIX程序员手册:平时在使用linux时,查看命令的帮助经常使用man,如 man ls显示ls命令的帮助信息,man显示的内容即为UNIX程序员手册的内容。这些帮助信息就是UNIX程序员手册中的内容。UNIX程序员手册被分为8个部分,每个部分描述不同的内容,如man ls后,会在ls后显示序号ls(1)。其表示的第1部分(命令)的内容。
手册搜索时会从第一部分顺序搜索,如使用man sleep,会先显示手册的第一部分,即一般命令部分。
如果你想看的是库函数的sleep帮助,即第三部分的sleep,则需使用man 3 sleep。
线程:进程有id,线程也有id。但是,线程id只在它所属的进程内起作用,一个进程中的线程id与另一个线程没有关系。当在一进程中对某个特定的线程进行处理时,可以使用线程id访问它。
用户和组:口令文件/etc/passwd存放用户名和用户id的对应关系,而组文件/etc/group存放组名和组id的对应关系,每个文件都包含有所属用户和所属组的信息,使用整数id而不是字符串名来保存更节省空间,同时比较字符串比比较整数更加耗时。
时间值:有两种:
- 日历时间 :从1970-1-1 00:00:00 到现在的秒数,用来记录文件最近一次修改时间
- 进程时间:衡量进程执行的时间,即时钟时间,时钟时间=用户CPU时间+系统CPU时间,使用time命令可以分析
限制
为了保持程序在任何unix-like系统上能运行,需要做一些限制,如CHAR_BIT限制了char的最大最小位数,限制有两种类型。
- 编译时限制:编译时即可确定的值,如int的最大值,数值规定在
中。 - 运行时限制:需要在运行时才能获取,使用sysconf获取。如获取程序最大打开文件数sysconf(_SC_OPEN_MAX);
可以使用shell提供的内建命令ulimit -a查看限制大小。
基本系统数据类型
头文件<sys/types.h>定义了某些POSIX系统实现有关的数据类型,称为基本系统数据类型。他们都是使用typedef定义,名称一般以 _t 结尾。使用这种方式定义一个变量的好处是,不用考虑不同系统的差异,如int64_t在任何系统中都是64位的,而如果使用int定义,可能并非是你想的32位的int类型。
文件I/O
文件I/O是第三章,从本章开始介绍POSIX和库函数,可以当作工具书来参考,建议快速跳读。
unix下的7种文件类型:可以使用file <文件名>查看文件名>
- 普通文件:如文本文件和二进制文件
- 目录文件:目录
- 符号链接:如/dev/cdrom
- 字符特殊文件:/dev/tty
- 块特殊文件:如/dev/sr0
- 套接字:如/dev/log
- FIFO:又叫命名管道named pipe,如/var/lib/oprofile/opd_pipe
以上顺序也是各类文件在操作系统中的数量占比顺序,普通文件占比最多。
文件中的空洞:空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。表现为使用ll
命令可能显示文件为8MB字节,但使用du -s
命令只占用了139KB的磁盘,这是因为文件空洞并不占磁盘空间。如果将该文件复制,则空洞会被填满,du -s
命令就比8MB要大了,因为8MB文件存放在多个块,需要额外的空间存放块指针。
标准I/O库
标准I/O有三种类型的缓冲:
- 全缓冲:填满缓冲区才会执行IO操作,或者显式调用flush
- 行缓冲:填满缓冲区或遇到换行符则执行IO操作
- 不带缓冲:立即执行IO操作,写入磁盘等
标准IO的代替软件:标准IO效率很有问题,因为标准IO使用read(int fd, void *buf, size_t count)
系统调用时,是将硬盘文件内容复制到内核缓冲区中,再从内核缓冲区复制到用户缓冲区中,进行了两次复制。快速IO(fio)避免了这一点,grep则是使用了这个功能,使得速度提高了3倍。
参考文档:https://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html
磁盘IO的优化技术有多种,可以看上面的参考文档,IO优化在网络编程中很重要,可以显著提高网络的响应速度。
进程环境
C程序是怎么启动并调用main函数的呢?使用exec系统调用启动,在调用main函数前,先执行一个特殊的启动历程,启动例程从内核获取命令行参数和环境变量值,做好前期准备,接着调用main函数。
进程控制
进程id为0的进程通常是调度进程,该进程是内核的一部分,因此也被称为系统进程。进程id为1通常是init进程,在自举过程结束时由内核调用。init进程通常读取与系统相关的初始化文件,如/etc/init.d中的文件,它是一个普通的用户进程并以超级用户特权运行。
解析器文件
所有的UNIX系统都支持解析器文件,解析器文件的开头行是#!
,如shell脚本文件为#! /bin/bash
,感叹号和pathname中间的空格是可选的。这种文件的识别是由内核作为exec系统调用处理的一部分完成的。
进程关系
进程组:每个进程除了有一个进程id之外,还属于一个进程组。同一进程组的多个进程接收来自同一终端的各种信号。每个进程组有一个组长进程,其pid等于组id。
会话:会话是一个或多个进程组的集合,如下图所示。
通常是由shell的管道将几个进程编成一组的,如:
proc1 | proc2
proc3 | proc4 | proc5
守护进程
如下图,ps -ef
命令输出的内容,父进程id为0的各进程通常是内核进程,使用方括号括起来表示,init进程不是内核进程。TTY列为问号,表示这些进程没有终端连接。
守护进程的惯例:
- 如该守护进程使用锁文件,那么该文件通常位于/var/run中,命名为name.pid。
- 若该守护进程支持配置文件,通常位于/etc中,命名为name.conf。
- 守护进程可用命令行启动,但通常是由系统初始化脚本(/etc/rc*和/etc/init.d/)启动的。
- 守护进程启动时会读取配置文件,但以后不再查看它,如果配置文件被修改,需要重启守护进程。为避免这种麻烦,可以让守护进程捕捉SIGHUP信号。
客户进程-服务器进程模型
守护进程是一种生存期长的进程,它们常常在系统引导装入时启动,仅在系统关闭时终止。守护进程常常用作服务器进程。客户端请求服务器时,一种很常见的做法是,在服务器中调用fork然后exec另一个进程来向客户端提供服务。
高级I/O
高级IO有多种概念:
- 非阻塞IO
- 记录锁
- IO多路转接(select和poll函数)
- 异步IO
- readv和writev以及存储映射IO(mmap)
很多高性能的框架大量使用了IO多路转接,IO多路转接的原理是,先构造一张我们感兴趣的描述符列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行IO时,该函数才返回。UNIX中实现这种功能的函数有:select, pselect, poll。
进程间通信IPC
IPC的方式有:
- 管道:管道是UNIX系统IPC最古老的方式,进程执行时,可使用pipe函数生成管道,该函数返回了管道两端的两个文件描述符,表示管道的读和写。父进程向子进程的管道写数据时,可以关闭父进程的读管道。
- FIFO:命名管道,未命名的管道只能在两个进程之间使用,而且这两个进程还需要有一个共同创建他们的祖先进程。但是通过FIFO,不相关的进程也能实现通信。
- 消息队列
- 信号量:信号量与上面的3种IPC通信方式不同,它是一个计数器。
- 共享存储:共享存储中,数据不需要在客户进程和服务进程间复制,所以这是最快的IPC方式。
网络IPC:套接字
上面说的进程间通信针对单机,套接字接口将允许进程通过网络通信,也允许进程单机通信。套接字是通信端点的抽象,正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字。套接字描述符在UNIX上被当作是一种文件描述符。
int socket(int domain, int type, int protocol);
protocol一般为0表示默认协议,在AF_INET域中,套接字类型为SOCK_DGRAM的默认协议是UDP。
高级进程间通信:UNIX域套接字
域套接字可以在同一计算机上允许的两个进程之间传送打开文件的描述符。服务进程可以使他们的打开文件描述符与指定的名字相关联,客户端可以使用这个名字与服务进程汇聚。虽然因特网域套接字也可实现同样的功能,但UNIX域套接字效率更高,它不执行协议处理,只复制数据。
UNIX域套接字就像套接字和管道的混合,如下图所示。一对相互连接的UNIX域套接字可以起到全双工管道的作用。
终端IO
此部分较难,暂时略过