第九章第二节 管道与信号量
管道
父进程调用pipe创建管道,fork之后管道的两端fd[0]与fd[1]在父子进程中均打开。此时,若要实现父进程向子进程写数据,则需要在父进程关闭fd[0],在子进程关闭fd[1]。
而若要实现父子进程的双向数据传输,可使用socketpair创建一个双向管道,父子进程各关闭一个,从而实现双向的数据传输。
管道只能用于有关联的进程间通信,对于没有关联的进程间通信,需要使用如下3种System V IPV,它们都使用一个全局唯一的键值来标识一条信道。
信号量
semget系统调用
semget系统调用创建一个新的信号量集,或者获取一个已经存在的信号量集。
1 | /* |
在semget用于创建信号量集后,与之关联的内核数据结构体semid_ds将被创建并初始化。semid_ds结构体定义如下。
1 | /* |
semget对semid_ds结构体的初始化如下。
- sem_perm.cuid与sem_perm.uid设置为调用进程的有效用户ID;
- sem_perm.cgid与sem_perm.gid设置为调用进程的有效组ID;
- sem_perm.mode低9位设置为flags参数的最低9位;
- sem_nsems设置为num;
- sem_otime设置为0;
- sem_ctime设置为当前系统时
semop系统调用
先介绍与每个信号量关联的一些重要的内核变量。
1 | unsigned short semval; //信号量的值 |
semop函数对信号量的操作其实就是对这些内核变量的操作;下面介绍semop函数,其定义如下。
1 | /* |
sembuf结构体的定义如下。
1 | /* |
sem_op与sem_flg搭配会有如下这些效果。
- sem_op大于0,semop将被操作的信号量的值semval增加sem_op(即V操作),此时要求调用进程对操作的信号量集拥有写权限。此外,若sem_flg设置了SEM_UNDO标志,则系统将更新进程的semadj变量(用以跟踪进程对信号量的修改情况);
- sem_op等于0,这表示一个”等待0”操作,该操作要求调用进程对操作的信号量集拥有读权限。
- 若此时信号量的值为0,则调用立即成功返回;
- 否则,调用失败返回或阻塞进程以等待信号量变为0;具体来说:若sem_flg设置了IPC_NOWAIT标志,则semop调用立即返回-1并设置errno为EAGAIN;
- 否则,信号量的semzcnt加1,进程被挂起直到以下3个条件之一满足:(1)信号量的semzcnt值为0,此时系统将该信号量的semzcnt减1;(2)被操作信号量所在信号量集被进程移除,此时semop调用失败返回, errno被设置为EIDRM;(3)调用被信号中断,此时semop调用失败返回,errno被设置为EINTR,同时系统将信号量的semzcnt减1;
- sem_op小于0,semop将被操作的信号量的值semval减去sem_op的绝对值(即P操作),此时要求调用进程对操作的信号量集具有写权限。此外,若em_flg设置了SEM_UNDO标志,操作系统将更新进程的semadj变量(同样用于跟踪进程对信号量的修改情况);
- 若semval+sem_op大于0,则semop调用成功返回,调用进程立即获得信号量;此外,操作系统将该信号量的值semval重置为semval+sem_op;
- 否则,则semop失败返回或阻塞进程以等待信号量可用;具体来说:若sem_flg设置了IPC_NOWAIT标志,semop立即返回-1,并设置errno为EAGAIN;
- 否则,则信号量的semncnt加1,进程被挂起直到以下3个条件之一满足:(1)semval+sem_op大于0,此时系统将该信号量的semncnt减1;此外,若sem_flg设置了SEM_UNDO标志,则系统将更新进程的semadj变量;(2)被操作信号量所在信号量集被进程移除,此时semop调用失败返回, errno被设置为EIDRM;(3)调用被信号中断,此时semop调用失败返回,errno被设置为EINTR,同时系统将信号量的semncnt减1;
semctl系统调用
semctl系统调用允许调用者对信号量进行直接控制。其定义如下。
1 | /* |
有些命令需要调用者传递第4个参数,参数类型由用户自行定义,但sys/sem.h中给出了推荐的格式。
1 | /* |
最后,semctl支持的所以命令如下图所示。
注:GETNCNT、GETPID、GETVAL、GETZCNT、SETVAL命令操作的是单个信号量,它是由标识符sem_id指定的信号量集中的第sem_ind个信号量;其他命令操作整个信号量集,此时sem_ind参数被忽略。
特殊键值IPC_PRIVATE
semget的调用者可以向其key参数传递一个特殊的键值IPC_PRIVATE(值为0),这样无论该信号量集是否存在,semget都将创建一个新的信号量。其实更应该称为IPC_NEW。