第十三章 多进程编程

第十三章 多进程编程

IPC(Inter-Process Communication)


13.1 fork 系统调用

Linux下创建新进程的系统调用是fork.

pid_t fork(void)

该函数的每次调用都返回两次,在父进程中返回的是子进程的PID,在子进程则返回0.该返回值是后续代码判断当前进程是父/子进程的依据,调用失败则返回-1,并设置error.

子进程与父进程有很多属性和原进程相同.如堆指针,栈指针和标志寄存器的值,以及继承下来的父进程的文件描述符(其引用会加1)等.
当然,也有许多属性会被赋予新的值,比如该进程的PPID被设置成原进程的PID,信号位图被清楚(原进程设置的信号处理函数不再对新进程起作用).

数据的复制利用了写时复制,即只有在任一进程(父进程或子进程)对数据执行了写操作复制才会发生.


13.2 exec 系列系统调用

需要在子进程中执行其他程序,即替换当前映像,可以使用exec系列函数之一,

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
  • file:文件名
  • path:可执行文件的完整路径
  • arg:可变参数
  • argv:参数数组
  • envp:设置新程序的环境变量

exec函数错误返回1,并设置error.若没出错,则原程序exec调用之后的代码都不会执行,因为此时原程序已经被exec所指定的程序完全替换了.

exec函数不会关闭原程序打开的描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC的属性.

例子:

execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);

13.3 处理僵尸程序

若父进程没有及时正确处理子进程的返回信息,子进程都会停留在僵尸态,并占据内核资源.

若拥有子进程的父进程异常终止,则子进程的PPID会被设置为1,即被init进程接管.

要正确处理子进程的退出,父进程需要等待子进程的结束,并获取子进程的返回信息,从而避免僵尸进程的产生,或者使子进程的僵尸态立即结束,有两组函数可以做到:

pid_t wait (int * status);
pid_t waitpid(pid_t pid,int * status,int options);

前者:将阻塞进程,直到该进程的某个子进程结束运行为止.

后者:只等待所指定的子进程,若pid的取值为-1,其行为就和wait一样.而且通过options可以设置其行为,当options是WNOHANG时waitpid调用将是非阻塞的:若指定的子进程没有结束或意外终止,则waitpid会立即返回0;若目标进程确实正常退出了,则waitpid返回子进程的PID.waitpid调用失败返回-1并设置error.

waitpid应该在进程结束的时候被调用以结束进程,故需要在子进程结束后给父进程发送信号,在父进程接收到信号后做出此操作以真正的结束子进程.


13.4 管道

管道也是父进程和子进程通信的常用手段.通常的管道只能单向的传输/接收数据,但是也可以利用socket的socketpair创建双向管道.


13.5 信号量

P,V 操作 : P原来是荷兰语passeren即传递,V是vrijeven,即释放.

P,V操作应该要被实现为原子操作,因为需要如下的功能(同时的)

  • 拥有检测变量是否为true/false

  • 若是则设置为false/true

Linux信号量的API主要包含了三个系统调用

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);
  • semget:创建一个新的信号集或者获取一个已经存在的信号量集..

  • semop:改变信号量的值.

  • semctl:对信号量进行直接控制.


13.6 共享内存

书中称共享内存是高效的IPC机制,因为它不涉及进程之间的任何数据传输,这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件.

关于Linux提供的共享内存API

int shmget(key_t key, size_t size, int shmflg); 
void *shmat(int shmid, const void *shmaddr,int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
  • shmget:创建一段新的共享内存或获取一段已经存在的共享内存.

  • shmat:共享内存被创建后不能立即访问,需要将它关联到进程的地址空间中.

  • shmdt:在使用完共享内存后,也需要将它从进程的地址空间中分离.

  • shmctl:控制共享内存的某些属性.


13.7 共享内存的POSIX方法

Linux提供了另外一种利用mmap在无关进程之间共享内存的方式,它无任何文件的支持,但它需要先使用如下函数进行操作

int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
  • shm_open:返回一个文件描述符,该文件描述符可用于后续的mmap调用,从而将共享内存关联到调用进程.

  • shm_unlink:由shm_open创建的共享内存对象使用完之后也需要被删除.此函数将指定的对象为等待删除,当所有使用该共享内存对象的进程都使用ummap将它从进程中分离之后,系统将销毁这个共享内存对象所占据的资源.


13.8 消息队列

消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式.

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msgget:创建消息队列,或者获取一个已有的消息队列.

  • msgsnd:把一条消息队列添加到消息队列.

  • msgrcv:从消息队列中获取消息.

  • msgctl:控制队列某些属性.


关于第十三章的总结

  • 复习了一下fork,exec族.

  • 三种System V进程间通信的方式,1)信号量2)共享内存3)消息队列

  • 进程间传递文件描述符的通用方法1)通过UNIX本地域即socketpair的PF_UNIX属性

  • 进程间通信常用的是管道.

  • 其实我觉得这些机制都是指的在某种特定情况下的通信,若是集群的话就不一定了.

  • 这一章代码我就不贴了,都是例子.使用的话查手册就好啦:)


From

Linux 高性能服务器编程 游双著 机械工业出版社

MarkdownPad2

2017/2/11 23:22:21

posted on 2017-02-13 16:43  leihui  阅读(179)  评论(0编辑  收藏  举报