Linux高性能服务器编程:多进程编程
1. fork系统调用
pid_t fork();
该函数的每次调用都返回两次,在父进程中返回子进程的PID,在子进程中则返回0. 该返回值是后续代码判断当前进程是父进程还是子进程的依据。fork调用失败时返回-1,并设置errno。
fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项有很多属性和原进程相同。比如堆指针、栈指针和标志寄存器的值。
子进程和父进程完全相同,同时它还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用的是所谓的写时复制,即只有在任一进程对数据执行了写操作时,复制才会发生(先是缺页中断,然后操作系统给子进程分配内存并复制父进程的数据)。
创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1.
2. exec系统调用
在子进程中执行其他程序,即替换当前进程映像。
3.处理僵尸进程
当子进程结束运行时,内核不会立即释放该进程的进程表表项,一满足父进程后续对该子进程退出信息的查询。在子进程结束运行之后,父进程读取其退出状态之前,称该子进程处于僵尸态。另一种使子进程进入僵尸态的情况是:父进程结束或异常终止,而子进程继续运行,此时子进程的PPID将被操作系统设置为1,即init进程。
4.管道
管道能在父子进程间传递数据,利用的是fork调用之后两个管道文件描述符(fd[0]和fd[1])都保持打开。
管道只能用于有关联的两个进程间的通信。
5. 信号量
使用二进制信号量。
使用一个普通变量来模拟二进制信号量是行不通的,因为所有高级语言都没有一个原子操作可以同时完成如下两部操作:
检测变量是否为true/false,如果是则再将它设置为false/true。
int semget(key_t key, int num_sems, int sem_flags);
创建一个新的信号量集,或者获取一个已经存在的信号量集。
key参数是一个键值,用来标识一个全局唯一的信号量集,就像文件名全局唯一地标识一个文件一样。要通过信号量通信的进程需要使用相同的键值来创建/获取该信号量。
int semop(int sim_id, struct sembuf* sem_ops, size_t num_sem_ops);
sim_id参数是由semget调用返回的信号量集标识符,用以指定被操作的目标信号量集。
int semctl(int sem_id, int sem_num, int command, ...); 允许调用者对信号量进行直接控制。
6. 共享内存
共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。但必须使用其他辅助手段来同步进程对共享内存的访问。
int shmget(key_t key, size_t size, int shmflg);
创建一段新的共享内存,或者获取已经存在的共享内存。
key是一个键值,用来标识一段全局唯一的共享内存。函数成功时返回一个正整数值,是共享内存的标识符。
void* shmat(int shm_id, const void* shm_addr, int shmflg); //关联
int shmdt(const void* shm_addr); //分离
7. 消息队列
消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。