什么是进程间通信
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)
无名管道(pipe)
无名的管道的特点
只能用于有血缘关系(父子进程、兄弟进程)之间的通信;
管道是单工通信模式,只能一端写入,从另一端读取;
单独构成一个独立的文件系统:管道独立于两端的进程,就是一个文件;但又不是一个普通文件,不属于某种文件系统,并且只存在于内存中;
函数原型
#include int pipe(int pipefd[2]);
pipefd[0]用于读取管道数据,pipefd[1]用于写入管道数据;
成功返回0,失败返回-1。
注意:管道不是一个普通的文件,不存在文件名,仅仅存在于内存中,所以使用 ls 命令看不见。无名管道是由进程创建的,因为没有名字,所以其他进程想要使用它,只能通过继承的方式打开,这也是为什么无名管道只能用于有关系的两个进程。
上图表示了父子进程通过无名管道进行进程间通信,父进程通过fd[1]写入数据,子进程通过fd[0]读取数据。
示例:
子进程写入数据,父进程读取数据。
#include #include #include #include #include int main(){ int fd[2] = {0}; //创建无名管道 int ret = pipe(fd); if (-1 == ret) { perror("pipe"); exit(1); } int num = 0; //无名管道只能用于父子进程之间的通信 pid_t pid = fork(); if (-1 == pid) { perror("fork"); exit(1); } else if (0 == pid) //子进程 { num++; printf("child num = %d\n", num); ret = write(fd[1], &num, sizeof(num)); //把变量写入管道 if (-1 == ret) { perror("write"); exit(1); } } else //父进程 { usleep(100000); ret = read(fd[0], &num, sizeof(num)); if (-1 == ret) { perror("read"); exit(1); } num++; printf("parent num = %d\n", num); int status; wait(&status); } return 0;}
无名管道读写特性
- 写端存在且管道中有数据时,read返回实际读取的字节数。当管道没有数据时,读进程阻塞(直到管道中有数据)。
- 写端不存在且管道中有数据时,read返回实际读取的字节数。当管道无数据时,read返回0。
- 读端存在且管道有空间时,write返回实际写入的字节数。当管道无空间时,写进程阻塞。
- 读端不存在不论管道是否有空间进程都会异常结束,会导致管道断裂。系统不允许写一个读端不存在的管道。
有名管道
有名管道不同于无名管道,它提供了一个路径名与之关联,所以有名管道是以文件的形式存在与文件系统中,任意两个进程都可以通过有名管道通信。
有名管道严格遵循先进先出,读取数据总是从开始处读,写入数据总是添加到末尾。
函数原型
#include #include int mkfifo(const char *pathname, mode_t mode);
pathname 管道文件名字;
mode 管道文件权限,如0666
成功返回0,失败返回-1。
示例
进程A创建管道,通过键盘往管道写入数据;
进程B从管道读取数据,读到“bye”退出。
fifo_write.c
#include #include #include #include #include #include #include #include #include int main(){ //创建有名管道(文件) int ret = mkfifo("fifo.tmp", 00400 | 00200); if (-1 == ret) { perror("mkfifo"); exit(1); } //默认是阻塞的 int fd = open("fifo.tmp", O_WRONLY); if (-1 == fd) { perror("open"); exit(1); } char buf[32] = {0}; while (1) { scanf("%s", buf); ret = write(fd, buf, strlen(buf)); if (-1 == ret) { perror("write"); exit(1); } if (!strcmp(buf, "bye")) { break; } memset(buf, 0, sizeof(buf)); } close(fd); unlink("fifo.tmp"); //删除管道文件 return 0;}
fifo_read.c
#include #include #include #include #include #include #include int main(){ //直接打开有名管道 int fd = open("fifo.tmp", O_RDONLY); if (-1 == fd) { perror("open"); exit(1); } int ret; char buf[32] = {0}; while (1) { ret = read(fd, buf, sizeof(buf)); if (-1 == ret) { perror("read"); exit(1); } if (!strcmp(buf, "bye")) { break; } printf("%s\n", buf); memset(buf, 0, sizeof(buf)); } close(fd); return 0;}
信号
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件。Linux中信号是对早期的unix信号机制进行了扩展。
Linux所有信号
常用的信号有
信号名 | 含义 | 默认操作 |
SIGHUB | 用户终端关闭时产生,发给和该终端关联的所有进程。 | 终止 |
SIGINT | 用户输入CTRL+C时产生,发给当前终端所有前台进程。 | 终止 |
SIGSEV | 该信号在非法访问内存时产生,如野指针、缓冲区溢出 | 终止 |
SIGPIPE | 当进程向一个没有读端的管道中写书数据时产生,代表管道断裂 | 终止 |
SIGKILL | 该进程用来结束进程,并且不能被捕捉和忽略 | 终止 |
SIGSTOP | 该信号用于暂停进程,并且不能被捕捉和忽略 | 暂停 |
SIGCONT | 该信号让进程进入运行态 | 继续 |
SIGALRM | 该信号用于通知进程定时器时间已到 | 终止 |
发送信号的命令
kill [-signal] pid
-signal 用于指定信号
pid 用于指定接收信号的进程
killall [-u user | prog
user 用于指定用户名
prog 用于指定进程名
信号相关的API
#include #include #include int kill(pid_t pid, int sig); //给指定进程发送信号int raise(int sig); //给进程本身发送信号unsigned int alarm(unsigned int seconds); //指定时间后给进程本身发送SIGALRM信号typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler); //设置信号处理函数
函数signal()非常重要,可以指定收到信号后的响应动作。
signum 要设置的信号种类;
handler 信号处理函数(收到信号后调用)。
示例1
输入进程号,杀死对应的进程。
#include #include #include int main(){ pid_t pid; scanf("%d", &pid); //kill(pid, 2); kill(pid, SIGINT); //给pid进程发送SIGINT信号 return 0;}
示例2
每隔固定时间发送SIGALRM信号,收到信号打印。
#include #include #include void handler(int sig){ printf("收到信号 %d\n", sig);}void show(int sig){ printf("helloworld\n"); alarm(2);}int main(){ //signal(SIGINT, SIG_IGN); //忽略SIGINT信号 //signal(SIGKILL, SIG_IGN); //SIGKILL和SIGSTOP不能被忽略 signal(SIGINT, handler); signal(SIGALRM, show); alarm(2); //2秒后给系统发送SIGALRM信号,只发一次 while (1); return 0;}