49-进程通信初步
来源:互联网 发布:淘宝上的店铺收藏链接 编辑:程序博客网 时间:2024/04/29 19:24
错综复杂的信号专题终于结束,能写到这里,感觉真不容易,甚至有点小激动,因为接下来进入进程间通信这个老生常谈的大专题。希望能有信心写好,因为我自己也有很多地方没有弄清晰,希望借此笔记来提升一下自己。
进程间通信,也就是大家常说的 IPC(Inter Process Communication),指的是不同的进程间进行交流,本质上就是进程之间发送和接收数据。
本质上,信号也是属于进程间通信的一种,只不过信号这一块的内容实在是太多,所以自成一体了。
作为进程通信的初步,我并不想以“管道通信”开始。如果就这样开始了,我感觉会有些突兀,循序渐近的学习方式才能让人接受并理解。
1. 进程间如何通信?
为什么进程需要通信?通信有这么难吗?还需要一个专题来讲?
没错,进程间通信就是这么难这么麻烦。通信这么难的原因,在之前的《进程基础》专题已经很详细的说明过了,因为不同进程间的进程空间是独立的。对于 linux 来说,进程的 0-3GB 空间是互不相干的,3GB-4GB 是内核空间,属于所有进程间共享地带。下面这张图不知你是否还记得,它形象的说明了进程空间的独立性,以及内核空间的共享性。
图1 进程的用户空间与内核空间
所以,内核的共享特性,给进程的通信带来了可能。本专题将围绕这一特性,对常见的进程通信进行说明和演示。不过在此之前,我们先讲讲不通过内核的方式来实现进程通信。
有同学会好奇,既然内核共享的话,直接向 3GB-4GB 内核空间写数据不就行了吗?No No No,这是一种幼稚的想法,用户程序是不能读写内核空间数据的。
2. 设计我们自己的进程间通信方式
尽管到目前为止,你还未学过任何进程间通信的手段(不算信号的话),你也能完成进程间通信。最直接的方式就是使用两个进程读写同一个文件了。
我们要实现的功能是,进程 A 从标准输入读取字符,然后“发送给”进程 B,进程 B 接收到数据后,将字符中的小写转换成大写后打印到屏幕。
有两个问题我们需要解决:
- 进程 A 和进程 B 需要约定好,读写哪个文件
- 进程 A 需要告诉进程 B,它已经将数据写入了那个约定好的文件
第一个问题很容易解决,只要使用同一个文件就好,名字么,随便取。
第二个问题,进程 A 如何告诉进程 B 它已经写好数据了?如果能告诉进程 B 的话,那还用得着使用文件这么麻烦的手段来通信吗?感觉这就是一个悖论。其实不然,进程 A 只要能通知到进程 B 就行了,回忆我们前面学的信号机制,如果进程 A 给进程 B 发送信号,问题不就解决了吗?
虽然信号也属于进程间通信,可是信号能传递的信息量实在是太少太少了,尽管有带参数的信号,但是这也解决不了问题。
最终,我们讨论的方案应该会是这样:
- 进程 A 创建一个文件 tmp,并向 tmp 写入数据。
- 进程 A 写完数据后关闭 tmp,并向进程 B 发送信号 SIGUSR1.
- 进程 B 接收到信号后,知道进程 A 已经写完数据,于是打开文件 tmp 读取数据。
- 进程 B 读取完数据后关闭 tmp 文件,并把 tmp 文件删除。
- 进程 B 把读取到的数据中的字符全部转换成大写打印到屏幕。
3. 代码清单
3.1 发送数据程序 sender
// sender.c#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <signal.h>#include <stdio.h>#include <stdlib.h>int main(int argc, char* argv[]) { // 要想发送信号,必须知道另一个进程的进程 id 号,所以这里通过参数将进程 id 传进来 if (argc < 2) { printf("usage: %s <pid>", argv[0]); return 1; } pid_t pid = atoi(argv[1]); char buf[64] = { 0 }; int n = 0; while(1) { // 从标准输入中读取数据,并写到文件中 if ((n = read(STDIN_FILENO, buf, 64)) > 0) { int fd = open("tmp", O_WRONLY | O_CREAT | O_EXCL, 0664); if (fd < 0) { perror("open"); continue; } write(fd, buf, n); // 写完数据后,向接收进程发送 SIGUSR1 信号 close(fd); if (kill(pid, SIGUSR1) < 0) { perror("kill"); } // 如果用户输入 q,就关闭程序 if (*buf == 'q') return 0; } } return 0;}
3.2 recver
// recver.c#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <ctype.h>// 信号处理函数,从文件中读取数据,并转换成大写打印到屏幕void handler(int sig) { char buf[64]; int i; int fd = open("tmp", O_RDONLY); if (fd < 0) { perror("open"); return; } int n = 0; if ((n = read(fd, buf, 64)) < 0) { perror("read"); close(fd); return; } close(fd); unlink("tmp"); // 读取完成后将文件删除 for (i = 0; i < n; ++i) putchar(toupper(buf[i])); // 将数据转换成大写并打印到屏幕。toupper 是 C 库函数,声明于 ctype.h 文件中 if (*buf == 'q') exit(0); // 如果收到的数据以 q 开头就退出}int main() { printf("I'm %d\n", getpid()); // 注册 SIGUSR1 信号 struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGUSR1, &act, NULL) < 0) { perror("sigaction"); return 1; } // main 函数进入休眠 while(1) pause();}
3.3 编译和运行
- 编译
$ gcc sender.c -o sender$ gcc recver.c -o recver
- 运行
$ ./recver
屏幕显示 recver 进程的 id 号:
I'm 2836
再开启另一个终端,执行
$ ./sender 2847
接下来在 sender 控制界面输入字符,在 recver 一端会打印,结果如下图:
4. 总结
- 初步理解进程间通信原理(通过内核或文件)
- 知道如何通过文件进行进程间通信
思考:
通过文件进行进程通信,和使用内核进行进程间通信有什么共同点?它们的区别在哪?
- 49-进程通信初步
- 进程间通信方法初步总结
- 进程间通信之共享内存初步
- 进程初步
- win32 线程通信初步
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- 进程通信
- UGUI是否也能通过改变精灵的名字来替换精灵
- cookie封装插件
- android sqlite 完全用replace替代 insert 和 update 操作
- ajax封装
- jQuery导入项目中红叉报错的问题
- 49-进程通信初步
- ajax
- cs231n:assignment2——python文件:layers.py
- Spatial Influence - Measuring Followship in the Real World
- C#开发环境搭建/C#介绍/helloWorld代码——day01
- jsonp封装
- maven已经存在,但是idea提示 Invalid classes root
- 监听器
- Linux中常用的命令以及使用方法案例