文章目录
  1. 管道
  2. 信号量
    1. semget系统调用
    2. semop系统调用
    3. semctl系统调用
    4. 特殊键值IPC_PRIVATE

管道

父进程调用pipe创建管道,fork之后管道的两端fd[0]与fd[1]在父子进程中均打开。此时,若要实现父进程向子进程写数据,则需要在父进程关闭fd[0],在子进程关闭fd[1]。

而若要实现父子进程的双向数据传输,可使用socketpair创建一个双向管道,父子进程各关闭一个,从而实现双向的数据传输。

管道只能用于有关联的进程间通信,对于没有关联的进程间通信,需要使用如下3种System V IPV,它们都使用一个全局唯一的键值来标识一条信道。

信号量

semget系统调用

semget系统调用创建一个新的信号量集,或者获取一个已经存在的信号量集。

1
2
3
4
5
6
7
8
9
10
/*
引用方式: #include <sys/sem.h>
key: 键值, 用来标识一个全局唯一的信号量集; 两个进程要使用信号量通信, 则需要使用相同的键值来创建/获取该信号量
num: 指定要创建/获取的信号量集中信号量的数目; 创建: 必须设置 || 获取: 可设置为0
flags: 指定一组标志, 其低端的9比特为该信号量的权限, 格式含义均与系统调用open的mode参数相同, 可与下面标志按位或
IPC_CREAT: 创建新的信号量集
IPC_EXCL: 确保创建的信号量集唯一(若信号量集已经存在, 则semget返回-1并设置errno为EEXIST)
返回一个正整数作为信号量集的标识符: 成功 || 返回-1并设置errno: 失败
*/
int semget(key_t key, int num, int flags);

在semget用于创建信号量集后,与之关联的内核数据结构体semid_ds将被创建并初始化。semid_ds结构体定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
引用方式: #include <sys/sem.h>
__key: 键值
uid: 所有者的有效用户ID
gid: 所有者的有效组ID
cuid: 创建者的有效用户ID
cgid: 创建者的有效用户ID
mode: 访问权限
*/
struct ipc_perm{
__key_t __key;
__uid_t uid;
__gid_t gid;
__uid_t cuid;
__gid_t cgid;
unsigned short int mode;
unsigned short int __pad1;
unsigned short int __seq;
unsigned short int __pad2;
__syscall_ulong_t __unused1;
__syscall_ulong_t __unused2;
};
/*
引用方式: #include <sys/sem.h>
sem_perm: 信号量的操作权限
sem_nsems: 信号量集中信号量的数目
sem_otime: 最后一次调用semop的时间
sem_ctime: 最后一次调用semctl的时间
*/
struct semid_ds{
struct ipc_perm sem_perm;
__time_t sem_otime;
__syscall_ulong_t __unused1;
__time_t sem_ctime;
__syscall_ulong_t __unused2;
__syscall_ulong_t sem_nsems;
__syscall_ulong_t __unused3;
__syscall_ulong_t __unused4;
};

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
2
3
4
5
6
7
unsigned short semval; //信号量的值

unsigned short semzcnt; //等待信号值变为0的进程数量

unsigned short semncnt; //等待信号量值增加的进程数量

pid_t sempid; //最后一次执行semop操作的进程ID

semop函数对信号量的操作其实就是对这些内核变量的操作;下面介绍semop函数,其定义如下。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/sem.h>
id: 目标信号量集的标识符(由semget函数返回)
sem_ops: 控制semop函数的行为, 详见下文
num_sem_ops: 指定要执行的操作个数(即sem_ops数组中元素的个数)
返回0: 成功 || 返回-1并设置errno: 失败(此时数组sem_ops中的操作均不被执行)
*/
int semop(int id, struct sembuf * sem_ops, size_t num_sem_ops);

sembuf结构体的定义如下。

1
2
3
4
5
6
7
8
9
10
11
/*
引用方式: #include <sys/sem.h>
sem_num: 信号量在信号量集中的编号
sem_op: 指定操作类型, 详见下文; 同时每种类型的操作行为还会受到sem_flg的影响
sem_flg: IPC_NOWAIT: semop调用是非阻塞的 || SEM_UNDO: 进程退出时取消正在进行的semop操作
*/
struct sembuf{
unsigned short int sem_num;
short int sem_op;
short int sem_flg;
};

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
2
3
4
5
6
7
8
/*
引用方式: #include <sys/sem.h>
id: 目标信号量集的标识符(由semget函数返回)
sem_ind: 被操作的信号量在信号量集中的编号
command: 指定执行的命令
返回值取决于command || 返回-1并设置errno: 失败
*/
int semctl(int id, int sem_ind, int command, ...);

有些命令需要调用者传递第4个参数,参数类型由用户自行定义,但sys/sem.h中给出了推荐的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
引用方式: #include <sys/sem.h>
semmap: Linux内核未使用
semmni: 系统最多可以拥有的信号量集数目
semmns: 系统最多可以拥有的信号量数目
semmnu: Linux内核未使用
semmsl: 一个信号量集最多可以包含的信号量数目
semopm: semop一次最多可以执行的sem_op操作数目
semume: Linux内核未使用
semusz: sem_undo结构体的大小
semvmx: 最多允许的信号量值
semaem: 最多允许的UNDO次数(带SEM_UNDO标志的sem_op操作的次数)
*/
struct seminfo{
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};

/*
引用方式: 未定义(只是推荐使用)
val: 用于SETVAL命令
buf: 用于IPC_STAT与IPC_SET命令
array: 用于GETALL与SETALL命令
__buf: 用于IPC_INFO命令
*/
union semun{
int val;
struct semid_ds * buf;
unsigned short int * array;
struct seminfo * __buf;
};

最后,semctl支持的所以命令如下图所示。

 

注:GETNCNT、GETPID、GETVAL、GETZCNT、SETVAL命令操作的是单个信号量,它是由标识符sem_id指定的信号量集中的第sem_ind个信号量;其他命令操作整个信号量集,此时sem_ind参数被忽略。

特殊键值IPC_PRIVATE

semget的调用者可以向其key参数传递一个特殊的键值IPC_PRIVATE(值为0),这样无论该信号量集是否存在,semget都将创建一个新的信号量。其实更应该称为IPC_NEW。