1. 多进程编程
1.1 fork
#include <sys/types.h>
#include <unistd.h>
// 调用失败返回 -1 设置 errno
pid_t fork( void );
子进程返回 0,父进程返回子进程 PID;
信号位图被清除(父进程的信号处理函数不再对新进程起作用)
1.2 exec 系列系统调用
替换当前进程映像,一般情况下不会返回,出错后返回 -1 并设置 errno;
注意:exec 函数不会关闭原程序打开的文件描述符,除非该 fd 设置了 SOCK_CLOEXEC 属性;
1.3 处理僵尸进程
#include <sys/types.h>
#include <sys/wait.h>
// 调用失败返回 -1 设置 errno
pid_t wait( int* stat_loc );
pid_t waitpid( pid_t pid, int* stat_loc. int options );
waitpid
只等待 pid 指定的子进程;pid 取值为 -1,和 wait
相同,等待任意一个子进程结束;
options 取值是 WNOHANG 时,waitpid
是非阻塞的:pid 指定的子进程没有结束或意外终止,立即返回 0;子进程正常退出,返回子进程 PID;
1.4 信号量
1.4.1 semget
创建一个新的信号量集,或者获取一个已经存在的信号量集;
#include <sys/sem.h>
// 成功返回一个正整数,标识信号量集
// 失败返回 -1 设置 errno
int semget( key_t key, int num_sems. int sem_flags );
key 用来标识一个全局唯一的信号量集;传递特殊键值 IPC_PRIVATE(值为 0)可以保证总是创建新的信号量集;
num_sems 指定信号量集中信号量的数目;(若是创建,必须指定;若是获取,设为 0)
sem_flags 指定该信号量的权限, 后 9 位与open
中的 mode 一致;
#include <sys/sem.h>
struct ipc_perm
{
key_t key; /* 键值 */
uid_t uid; /* 所有者的有效用户 ID */
gid_t gid; /* 所有者的有效组 ID */
uid_t cuid; /* 创建者的有效用户 ID */
gid_t cgid; /* 创建者的有效组 ID */
mode_t mode; /* 访问权限 */
/* 省略其他填充字段 */
};
struct semid_ds
{
struct ipc_perm sem_perm; /* 信号量的操作权限 */
unsigned long int sem_nsems; /* 信号量集的信号量数目 */
time_t sem_otime; /* 最后一次调用 semop 的时间 */
time_t sem_ctime; /* 最后一次调用 semctl 的时间 */
/* 键值 */
};
semid_ds 是与信号量集相关联的内核数据结构,semget
作用是创建并初始化它,会将 sem_otime 设为 0,sem_ctime 设为当前系统时间;
1.4.2 semop
改变信号量的值,是原子操作;
unsigned short semval; /* 信号量的值 */
unsigned short semzcnt; /* 等待信号值变为 0 的进程数量 */
unsigned short semncnt; /* 等待信号值增加的进程数量 */
pid_t sempid; /* 最后一次执行 semop 操作的进程 ID */
int semop( int sem_id, struct sembuf* sem_ops, size_t num_sem_ops );
sem_id 是 semget
返回的信号量集标识符;sem_ops 指向 sembuf 数组;
struct sembuf
{
unsigned short int sem_num;
short int sem_op;
short int sem_flg;
}
sem_num 为信号集中信号量的编号;
SEM_UNDO 的含义是,当进程退出时取消正在进行的 semop
操作,会更新进程的 semadj 变量,,来跟踪进程对信号量的修改情况;
1.4.3 semctl
对信号量直接控制
int semctl( int sem_id, int sem_num, int command, ... );
command 为要执行的命令,有些命令需要第 4 个参数,推荐如下形式
union semun
{
int val; /*用于 SETVAL 命令*/
struct semid_ds* buf; /*用于 IPC_STAT 和 IPC_SET 命令*/
unsigned short* array; /*用于 GETALL 和 SETALL 命令*/
struct seminfo* __buf; /*用于 IPC_INFO 命令*/
};
常用命令为
命令 | 含义 | 成功时返回值 |
---|---|---|
IPC_RMID | 移除信号量集,唤醒所有等待该信号量集的进程(semop 返回错误,errno 为 EIDRM) | 0 |
SETVAL | 将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新 | 0 |
1.5 共享内存
最高效的 IPC 机制;
1.5.1 shmget
创建一段新的共享内存或获取一段已经存在的共享内存;
#include <sys/shm.h>
// 成功返回正整数,为共享内存的标识;失败返回 -1 设置 errno
int shmget( key_t key, size_t size, int shmflg );
这三个参数与 semget
含义相同,size 指定共享内存的大小,单位是字节;
shmflg 支持额外的两个标志:
- SHM_HUGETLB;系统使用“大页面”分配共享内存,类似于
mmap
的 MAP_HUGETLB 标志; - SHM_NORESERVE;不为共享内存保留交换分区,类似于
mmap
的 MAP_NORESERVE 标志;这样当物理内存不足时,对该共享内存执行写操作将触发 SIGSEGV 信号;
shmget
创建共享内存,所有字节都被初始化为 0,与之关联的内核数据结构为 shmid_ds 被创建和初始化;
struct shmid_ds
{
struct ipc_perm shm_perm; /* 共享内存的操作权限 */
size_t shm_segsz; /* 共享内存的大小,单位是字节 */
__time_t shm_atime; /* 对这段内存最后一次调用 shmat 的时间 */
__time_t shm_dtime; /* 对这段内存最后一次调用 shmdt 的时间 */
__time_t shm_ctime; /* 对这段内存最后一次调用 shmctl 的时间 */
__pid_t shm_cpid; /* 创建者的 PID */
__pid_t shm_lpid; /* 最后一次执行 shmat 或 shmdt 操作的进程 PID */
shmatt_t shm_nattach; /* 关联到此共享内存的进程数量 */
/* 省略填充字段 */
};
初始化时,shm_lpid、shm_nattach、shm_atime、shm_dtime 设置为 0,shm_ctime 设置为当前时间;
1.5.2 shmat 和 shmdt
关联/分离到进程的地址空间
void* shmat( int shm_id, const void* shm_addr, int shmflg );
int shmdt( const void* shm_addr );
shm_addr 指定将共享内存关联到进程的哪块空间地址;
- shm_addr 为 NULL,由操作系统选择,推荐的做法;
- shm_addr 非空,并且 SHM_RND 未设置,共享内存关联到 addr 指定地址处;
- shm_addr 非空,并且设置了 SHM_RND ,被关联的地址是 [shm_addr - (shm_addr%SHMLBA)];
shmflg 还支持如下标志:
- SHM_RDONLY;进程仅能读取共享内存中的内容;
- SHM_REMAP;如果地址 shm_addr 已经被关联到一段共享内存上,重新关联;
- SHM_EXEC;指定对共享内存段的执行权限;
shmat 成功返回被关联的地址,失败返回(void *) -1 并设置 errno;将 shm_nattach++,shm_lpid 设置为调用进程 PID,shm_atime 设置为当前时间;
shmdt 成功时返回 0,失败返回 -1 设置 errno;将 shm_nattach–,shm_lpid 设置为调用进程 PID,shm_dtime 设置为当前时间;
1.5.3 shmctl
// 失败时返回 -1 设置 errno
int shmctl( int shm_id, int command, struct shmid_ds* buf );
命令 | 含义 | 成功时返回值 |
---|---|---|
SETVAL | 将信号量的值设为 semun.val ,同时内核数据中的 semid_ds.sem_ctime 被更新 | 0 |
1.5.4 共享内存的 POSIX 方法
通过打开同一个文件, mmap
可以实现进程间的内存共享;
Linux 提高了另一种利用 mmap
在无关进程间共享内存的方式,无需任何文件的支持;
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
// 创建或打开一个 POSIX 共享内存对象
int shm_open( const char* name, int oflag, mode_t mode );
int shm_unlink( const char* name );
shm_open
成功返回一个文件描述符,该 fd 可用于后续的 mmap
调用,失败返回 -1 设置 errno。
shm_unlink
将 name 指定共享内存对象标记为等待删除;当所有使用该共享对象的进程都使用 ummap
将它从进程中分离后,系统将销毁该对象;
ftruncate(fd, length)
会将参数 fd 指定的文件大小改为参数 length 指定的大小。
1.6 消息队列
两个进程间传递二进制块数据一种简单有效的方式;
每个数据块都有一个特定的类型,接收方可以根据类型来选择地接收数据;
1.6.1 msgget
#include <sys/msg.h>
int msgget( key_t key, int msgflg );
参数含义与 semget
一致,与之关联的内核数据结构 msqid_ds 被创建并初始化;
struct msqid_ds
{
struct ipc_perm msg_perm; /* 操作权限 */
time_t msg_stime; /* 最后一次调用 msgsnd 的时间 */
time_t msg_stime; /* 最后一次调用 msgrcv 的时间 */
time_t msg_stime; /* 最后一次被修改的时间 */
unsigned long __msg_cbytes; /* 消息队列中已有的字节数 */
msgqnum_t msg_qnum; /* 消息队列中已有的消息数 */
msglen_t msg_qbytes; /* 消息队列允许的最大字节数 */
pid_t msg_lspid; /* 最后执行 msgsnd 的进程 PID */
pid_t msg_lrpid; /* 最后执行 msgrcv 的进程 PID */
};