进程间通信
- 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
- 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件。
- 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
信号
通过注册信号与全局变量的结合达到进程间通信
优点:实时,不阻塞
缺点:不安全,内容固定
案例:
void func(int n)
{
printf("ping\n");
// how to send signal 4 to the second process?
}
void func2(int n)
{
printf("pong\n");
// how to send signal 3 to the first process?
}
int main()
{
pid_t pid;
int i;
for(i = 0; i < 2; i++){
pid = fork();
if(pid == 0){
if(i == 0){
signal(3, func);
}else{
signal(4, func2);
}
while(1);
}else{
if(i == 1){
sleep(3);
// how to send signal 3 to the first child process?
sleep(3);
// how to kill the two children?
}
}
}
return 0;
}
管道
- 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符:一个用于读取而另外一个用于写入。
- 最常见的IPC机制,通过pipe系统调用。
- 管道是单工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
- 通过打开两个管道来创建一个双向的管道
- 管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞。
- 当一个进程往管道中不断地写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道放满数据的则会报错。
匿名管道
- 在关系进程中进行(父进程和子进程、兄弟进程之间)
- 由pipe系统调用,管道由父进程建立。
- 管道位于内核空间,其实是一块缓存。
- int pipe(int fd[2]);
- 功能:创建匿名管道pipe
- 返回: 成功返回0 ,出错返回-1
- fd[0]: 为pipe的读端,用于读取管道;fd[1]: 为pipe的写端,用于写入管道
案例:
int main()
{
//匿名管道,第一步:定义一个整型的长度为2的数组
int fd[2] = { };
if(pipe(fd)){
perror("管道创建失败\n");
exit(-1);
}
int pid = fork();
if(pid == 0){//子进程 -- 读消息
close(fd[1]);
while(1){
printf("子进程正在等待消息...\n");
char buffer[1024] = " ";
read(fd[0],buffer,1024) ;
printf("读取到%s\n",buffer);
if(strcmp(buffer,"end") == 0) break;
}
close(fd[0]);
}else if(pid > 0){//父进程 -- 写消息
close(fd[0]);
while(1){
char buffer[1024] = " ";
printf("请输入你要发送的内容\n");
scanf("%s",buffer);
write(fd[1],buffer,strlen(buffer));
if(strcmp(buffer,"end") == 0) break;
}
wait(NULL);
}else{
perror("进程创建失败\n");
exit(-1);
}
return 0;
}
命名管道
- 两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在。
- 只要对FIFO有适当访问权限,FIFO可用在任何两个没有任何关系的进程之间通信。
- 本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在。
- 在文件系统中只有一个索引块存放文件的路径,没有数据块,所有数据存放在内核中。
- 命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞。
- 对FIFO的操作与操作普通文件一样,一般的文件I/O函数都可用于FIFO。
- 通过系统调用mkfifo创建
- int mkfifo(const char * pathname, mode_t mode);
- 功能:创建命名管道
- 返回:若成功则返回0 ,出错返回-1
案例:
int main()
{
if(access("pipe",F_OK)){
if(mkfifo("pipe",0664)){
perror("创建管道文件失败\n");
exit(-1);
}
}
int fd = open("pipe",O_RDWR);
if(fd < 0){
perror("打开文件失败\n");
exit(-1);
}
while(1){
char buffer[1024] = " ";
printf("请输入内容\n");
fgets(buffer,1024,stdin);
write(fd,buffer,strlen(buffer));
if(strcmp(buffer,"end") == 0) break;
}
close(fd);
return 0;
}
int main()
{
if(access("pipe",F_OK)){
if(mkfifo("pipe",0664)){
perror("创建管道文件失败\n");
exit(-1);
}
}
int fd = open("pipe",O_RDWR);
if(fd < 0){
perror("打开文件失败\n");
exit(-1);
}
while(1){
char buffer[1024] = " ";
read(fd,buffer,1024);
printf("%s\n",buffer);
if(strcmp(buffer,"end") == 0) break;
}
close(fd);
return 0;
}
popen管道
- The popen() function opens a process by creating a pipe, forking, and invoking the shell.
- FILE *popen(const char *cmdstring, const char *type);
- 返回值: 成功返回文件指针,出错返回NULL
- int pclose(FILE *fp);
- 返回值: cmdstring 的终止状态,出错返回-1
案例:
int main()
{
// FILE *popen(const char *command, const char *type);
// command:执行的shell命令
// type:打开方式。只能是 "r",或者"w"其他都会报错
// 本质上:创建一个管道,执行command命令,并将执行的结果保存到管道里面。
FILE* fp = popen("ls","r");
if(fp == NULL){
perror("创建管道失败\n");
exit(-1);
}
while(1){
char buffer[1024] = " ";
if(fgets(buffer,1024,fp) == NULL) break;
printf("%s",buffer);
}
printf("\n");
pclose(fp);
return 0;
}