进程通信(无名管道)
UNIX系统提供有名管道和无名管道两种数据通信方式。无名管道只能在有共同祖先(有亲缘关系)的进程间使用,有名管道可以在无亲缘关系的进程间使用。管道普遍存在于shell中。
无名管道为建立管道的进程及其子孙提供一条以比特流方式传送消息的通信管道,该管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,而很少启动外设。管道按FIFO(先进先出)方式传送消息,且只能单向传送消息。如果需要父进程向子进程、子进程向父进程双向传递,可以设置两条管道。
write()和read()函数
write()和read()函数都是系统调用,我们熟知的文件读写操作fwrite()和fread()都是通过write()和read()来实现的。
区别在于read()每次读的数据是调用者要求的大小,且read()从内核缓冲区(操作系统开辟的一段空间用来存储磁盘上的数据)读取数据,所以每次调用read()会涉及到用户态与內核态之间的切换从而损耗一定的性能。而fread()每次都会从内核缓冲区读比要求更多的数据,然后放到应用进程缓冲区(首地址存在FILE结构体中),这样下次再读数据只需要到应用进程缓冲区中去取而无需过多的系统调用。
同理,write()每次写的数据是调用者要求的大小,write()将数据写到内核缓冲区中,依然涉及到用户态与內核态之间的切换,操作系统会定期地把这些存在内核缓冲区的数据写回磁盘中。而fwrite每次都会先把数据写入一个应用进程缓冲区,等到该缓冲区满了,或者调用类似调用fflush这种冲洗缓冲区的函数时,系统会调用write一次性把相应数据写进内核缓冲区中。同样减少了系统调用。
实际上write和read不会直接从磁盘文件中读写数据。例如read是从磁盘所关联的一个内核缓冲区读写数据。(因为磁盘读取数据速度实在太慢了,所以操作系统往往采用预读技术读取磁盘数据)。write也是仅将数据写进内核缓冲区,操作系统通过运行一个守护进程,定期的将内核缓冲区写入磁盘。
// include <unistd.h>
int write(int handle, void *buf, size_t len)
/*
将缓冲区的数据写入与handle相联的文件或设备中,handle是从creat、open、dup或dup2调用中得到的文件句柄。写入成功时返回写的字节数,错误时返回-1
*/
int read(int handle, void *buf, size_t len)
/*
从文件句柄handle所指向的文件中读取数据到所指向的缓存区中,读取成功时返回值为实际读取的字节数
*/
管道
利用UNIX系统提供的系统调用pipe(),可以建立一条单向同步通信管道。
// int pipe(int *fd)
#include <unistd.h>
int fd[2];
pipe(fd);
其中fd[1]为写入端,fd[0]为读出端
建立、终止子进程
###fork()函数
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略,且fork()是把进程当前的情况拷贝一份,建立子进程后,从fork()后继续执行
fork()函数仅仅调用一次,但可以理解为返回两次
- 对于父进程,fork()返回新创建的子进程的进程ID
- 对于子进程,fork()返回0
- 如果出现错误,fork()返回-1
可以将父子进程理解为链表,父进程指向子进程,子进程没有新的子进程,指向0
wait()函数
wait()函数用于使父进程(也就是调用wait()的进程)阻塞,直到一个子进程结束或该进程接收到一个指定的信号为止。如果该父进程没有子进程或它的子进程已经结束,则wait()就会立即返回。
#include <stdlib.h>
pit_d wait(int *status);
/*
status是一个整型指针,是该子进程退出的状态,如exit(0)、exit(1)等,status 可以设成NULL
如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1
*/
###waitpid()函数
waitpid()函数并不一定要等待一个终止的子进程,它还有若干选项,wait()函数只是waitpid()函数的一个特例。
#include <stdlib.h>
pid_t waitpid(pid_t pid, int *status, int options);
/*
pid > 0,只等待进程ID等于pid的子进程,不管其他子进程是否结束
pid = -1,等待任何一个子进程退出,此时和wait作用相同
pid = 0,等待其组 ID 等于调用进程的组 ID 的任一子进程
pid < -1,等待其组 ID 等于 pid 的绝对值的任一子进程
status,作用同wait()
options:
0,同wait(),阻塞父进程,等待子进程退出
WNOHANG:若由 pid 指定的子进程没有结束,则 waitpid()不阻塞而立即返回。此时返回值为0
WUNTRACED:为了实现某种操作,由pid指定的任一子进程已被暂停,且其状态自暂停依赖还未报告过,则返回其状态
返回值:
正常执行,返回子进程识别码(PID)
使用WNOHANG且没有子进程退出,返回0
调用出错返回-1
*/
exit()函数
exit()用于结束当前进程/当前程序,在整个程序中,只要调用exit(),即结束程序。
exit(0)表示正常退出;
exit(x)(x不为0)都表示异常退出,这个x是返回给操作系统(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。
与return的区别:
按照ANSI C,在最初调用的main()中使用return和exit()的效果相同。但要注意这里所说的是"最初调用"。如果main()在一个递归程序中,exit()仍然会终止程序;但return将控制权移交给递归的前一级,直到最初的那一级,此时return才会终止程序。return和exit()的另一个区别在于,即使在除main()之外的函数中调用exit(),它也将终止程序。
#include <stdlib.h>
exit(0);
_exit()函数
其与exit()功能类似,作用为直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。而exit()则在退出前加了若干道工序, exit()函数在调用exit这一系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件。
#include <unistd.h>
_exit(0);
实例演示
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int x, fd[2];
char buf[30], s[30];
pipe(fd); /* Creat a pipe */
while ((x = fork()) == -1) return 0; /* Return 0 when creat unsuccessfully */
if (x == 0) { /* Child process executes this line */
sprintf(buf, "This is an example\n");
write(fd[1], buf, 30); /* Write datas into pipe */
exit(0);
}
else { /* Father process executes thie line */
wait(0);
read(fd[0], s, 30); /* Read datas from pipe */
printf("%s", s);
}
return 0;
}
//result : This is an example
生成两个子进程,父进程来读取
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i, r, P1, P2, fd[2];
char buf[50], s[50];
pipe(fd);
while ((P1 = fork()) == -1) return 0;
if (P1 == 0) {
lockf(fd[1], 1, 0);
sprintf(buf, "child process P1 is sending messages\n");
printf("child process P1\n");
write(fd[1], buf, 50);
sleep(5);
lockf(fd[1], 0, 0);
exit(0);
}
else {
while ((P2 = fork()) == -1) return 0;
if (P2 == 0) {
lockf(fd[1], 1, 0);
sprintf(buf, "child process P2 is sending messages\n");
printf("child process P2\n");
write(fd[1], buf, 50);
sleep(5);
lockf(fd[1], 0, 0);
exit(0);
}
wait(0);
if ((r = read(fd[0], s, 50)) == -1) {
printf("can't read pipe\n");
}
else {
printf("%s", s);
}
wait(0);
if ((r = read(fd[0], s, 50)) == -1) {
printf("can't read pipe\n");
}
else {
printf("%s", s);
}
exit(0);
}
return 0;
}
/*
Result:
child process P1
child process P2
child process P1 is sending messages
child process P2 is sending messages
*/