1.管道
匿名管道:匿名管道用于进程间通信,且仅限于本地父子进程之间的通信 管道符号 |
进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。
管道的接口 pipe:int pipe(int pipefd[2]); pipe2: int pipe2(int pipefd[2], int flags);pipe2函数的第二个参数用于设置选项
特性:亲缘性 单向通信 大小 pipe_size(原子性)阻塞属性 非阻塞属性
管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。
命名管道: 创建 mkfifo命令 mkfifo函数 不需要具有亲缘性
int mkfifo(const char *pathname, mode_t mode);mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。命名管道创建成功,返回0。命名管道创建失败,返回-1。
匿名管道由pipe函数创建并打开。
命名管道由mkfifo函数创建,由open函数打开。
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。
服务端将信息从输入文件复制到服务端的临时缓冲区中。
将服务端临时缓冲区的信息复制到管道中。
客户端将信息从管道复制到客户端的缓冲区中。
将客户端临时缓冲区的信息复制到输出文件中。
共享内存: 共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存。这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成。
在物理内存当中申请共享内存空间。
将申请到的共享内存挂接到地址空间,即建立映射关系。
将共享内存与地址空间去关联,即取消映射关系。
释放共享内存空间,即将物理内存归还给系统。
接口: shmget shmat shmdt shmctl
创建共享内存 int shmget(key_t key, size_t size, int shmflg);
第一个参数key,表示待创建共享内存在系统当中的唯一标识。第二个参数size,表示待创建共享内存的大小。第三个参数shmflg,表示创建共享内存的方式。shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)。shmget调用失败,返回-1。传入shmget函数的第一个参数key,需要我们使用ftok函数进行获取。ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。需要注意的是,pathname所指定的文件必须存在且可存取。
控制共享内存 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
第一个参数shmid,表示所控制共享内存的用户级标识符。第二个参数cmd,表示具体的控制动作。第三个参数buf,用于获取或设置所控制共享内存的数据结构。shmctl调用成功,返回0。shmctl调用失败,返回-1。
共享内存连接到进程地址空间 void *shmat(int shmid, const void *shmaddr, int shmflg);
第一个参数shmid,表示待关联共享内存的用户级标识符。第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。第三个参数shmflg,表示关联共享内存时设置的某些属性。shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。shmat调用失败,返回(void*)-1
取消共享内存与进程地址空间之间的关联 int shmdt(const void *shmaddr);
待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址。shmdt调用成功,返回0。shmdt调用失败,返回-1。
特性: 生命周期跟随操作系统 删除特性 覆盖写读访问
我们若是要将创建的共享内存释放,有两个方法,一就是使用命令释放共享内存,二就是在进程通信完毕后调用释放共享内存的函数进行释放。
从输入文件到共享内存。
从共享内存到输出文件。
3.消息队列
消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块。消息队列提供了一个从一个进程向另一个进程发送数据块的方法。每个数据块都被认为是有一个类型的,接收者进程接收的数据块可以有不同的类型值。和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。
接口: msgget msgsnd msgrcv msgctl
创建消息队列 int msgget(key_t key, int msgflg);
创建消息队列也需要使用ftok函数生成一个key值,这个key值作为msgget函数的第一个参数。
msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符)。
释放消息队列 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构。
向消息队列发送数据 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
第一个参数msqid,表示消息队列的用户级标识符。第二个参数msgp,表示待发送的数据块。第三个参数msgsz,表示所发送数据块的大小。第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。msgsnd调用成功,返回0。msgsnd调用失败,返回-1。
从消息队列获取数据 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
第一个参数msqid,表示消息队列的用户级标识符。第二个参数msgp,表示获取到的数据块,是一个输出型参数。第三个参数msgsz,表示要获取数据块的大小第四个参数msgtyp,表示要接收数据块的类型。msgsnd调用成功,返回实际获取到mtext数组中的字节数。msgsnd调用失败,返回-1。
特性:生命周期跟随操作系统内核
4.进程信号: 信号只能由操作系统发送,但信号发送的方式有多种。
信号产生:软件 硬件 kill命令
信号的注册:位图 sigqueue队列 非实时信号/实时信号的注册
信号的注销;非实时信号/实时信号的注销
信号的处理方式:默认处理 忽略处理 自定义处理
不能让操作系统直接去执行用户的代码,因为操作系统无法保证用户的代码是合法代码,即操作系统不信任任何用户。
信号的自定义处理:signal sigaction
sigaction函数可以读取和修改与指定信号相关联的处理动作
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum代表指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oldact指针非空,则通过oldact传出该信号原来的处理动作。
信号的捕捉流程:do_signal
信号的阻塞:阻塞位图 sigset_t block 信号的阻塞不会影响信号的注册
gcc的优化选项 volatile
自定义处理SIGCHLD