>## **==进程间通信==**
进程是一个独立的资源分配单位,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源,(例如打开的文件描述符)
进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信
进程间通信功能
数据传输:一个进程需要将发送给另一个进程
资源共享;多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件
进程控制:有些进程希望完全控制另一个进程的执行(如 debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变
linux操作系统支持的主要进程间通信的通信机制
进程间通信的实质
系统只要创建一个进程,就会给当前进程分配4G的虚拟内存(32位操作系统),虚拟内存不是说的内存条的空间,内存条的空间称之为物理内存,虚拟内存和物理内存之间存在映射关系
4G的虚拟内存分为3G的用户空间(0 ~ 3G)和1G(3~4G)的内核空间,
用户空间是进程所私有的,每一个进程的用户空间只能自己访问和使用,我们之前说的栈区,堆区,数据区,代码区,等都是用户空间的区域
内核空间是所有进程所公有的,也就意味着绝大多数进程间通信方式,本质就是对内核空间的操作
特殊的进程间通信方式:
socket通信可以实现不同主机的进程间通信,其他六个只能在一台主机的多个进程间通信
信号通信是唯一的一种异步通信机制,
共享内存是所有进程间通信方式中效率最高的它是直接对物理内存进行操作
信号
信号的概念
信号是软件中断,它是在软件层次上对中断机制的一种模拟
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件,信号是一种异步通信方式
进程不必等待信号的到达,进程也不知道信号什么时候到达,信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件
每个信号的名字都以字符SIC开头
每个信号和一个数字编码相对应,在头文件signum.h中,这些信号都被定义为正整数。
信号名定义路径;
/usr/include/x86_64-linux-gnu/bits/signum.h (ubuntu16.04)
在linux下,要想查看这些信号和编码的对应关系,可使用命令:kill -l
信号是由当前系统已经定义好的一些标识,每一个标识都会在特定的场合使用
并且都会对进程有一定的影响,当信号产生时,会对当前信号做出相应的操作
这些信号都是已经定义好的,我们不能自己再去创造,直接使用这些就可以
产生信号的方式
以下条件可以产生一个信号
1.当用户按某些终端键时,将产生信号
例如:
终端上按 Ctrl + c组合键通常产生中断信号SIGINT,终端上按Ctrl + \键通常产生中断信号SIGOUIT,终端上按Ctrl+z键通常产生中断信号SIGSTOP
2.硬件异常将产生信号
除数为0,无效的内存访问等,这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程
3.软件异常将产生信号
当检测到某种软件条件已发生,并将其通知有关进程时,产生信号
4.调用kill函数将发生信号
注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户
5.运行kill命令将发送信号
此程序实际上是使用kill函数来发送信号,也常用此命令终止一个失控的后台进程
信号的默认(缺省)处理方式
当进程中产生了一个信号,就会让当前进程做出一定的反应,默认处理进程的方式如下,
- 终止进程:当信号产生后,当前进程就会立即结束
- 缺省处理:当信号产生后,当前进程不做任何处理
- 停止进程:当信号产生后,使得当前进程停止
- 让停止的进程恢复运行:当信号产生后,停止的进程会恢复执行(变成后台进程)
注意每一个信号只有一个默认的处理方式
进程接收到信号后的处理方式
一个进程收到一个信号的时候,可以用如下方法进行处理
1.执行系统默认动作
对大多数信号来说,系统默认动作是用来终止该进程
2.忽略此信号
接收到此信号后没有任何动作
3.执行自定义信号处理函数
用用户定义的信号处理函数处理该信号
注意:SIGKILL和SIGSTOP这两个信号只能以默认的处理方式执行,不能忽略也不能自定义
常见的信号
信号 | 值 | 性质 | 默认处理方式 |
---|---|---|---|
SIGKILL | 9 | 当产生这个信号后,当前进程会退出 不会被缺省和捕捉 | 退出进程 |
SIGSTOP | 19 | 当产生这个信号后,当前进程会停止,不能被缺省和捕捉 | 停止进程 |
SIGINT | 2 | 键盘输入ctrl+c时产生信号 | 退出进程 |
SIGQUIT | 3 | 键盘输入ctrl+\时产生信号 | 退出进程 |
SIGTSTP | 20 | 键盘输入ctrl+z时产生的信号 | 停止进程 |
SIGCONT | 18 | 当产生当前信号后,当前停止的进程会恢复运行 | 停止的进程恢复运行 |
SIGALRM | 14 | 当调用alarm函数设置的时间到达时会产生当前信号 | 退出进程 |
SIGPIPE | 13 | 当管道破裂时,会产生当前信号 | 退出进程 |
SIGABRT | 6 | 当调用abort函数时会产生当前信号 | 退出进程 |
SIGCHLD | 17 | 当使用fork创建一个子进程时,如果子进程状态改变(退出)会产生当前信号 | 缺省 |
SIGUSR1 | 10 | 用户自定义信号,不会自动产生,只能使用kill函数或者命令给指定的进程发送当前信号 | 缺省 |
SIGUSR2 | 12 | 用户自定义信号,不会自动产生,只能使用kill函数或者命令给指定的进程发送当前信号 | 缺省 |
缺省就是忽略的意思,捕捉就是自定义的意思
kill函数
#include <sys/types.h>
#include <signal.h>
功能
给指定进程或进程组发送信号
参数
pid: 详见下面
signum: 信号的编号
返回值
成功返回0,失败返回-1
pid的取值有4种情况
pid>0: 将信号传送给进程ID为pid的进程
pid=0:将信号传送给当前进程所在进程组中的所有进程
pid=-1:将信号传给系统内所有的进程
pid<-1:将信号传给指定进程组的所有进程,这个进程组号等于pid的绝对值
具体实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0)//父进程的代码区
{
while(1)
{
printf("This is parent peocess\n");
sleep(1);
}
}
else//子进程的代码区
{
printf("This is son process\n");
//子进程在3秒之后,让父进程退出
sleep(3);
//使用kill给父进程发送信号,然后父进程接收到信号后直接退出就可以了
kill(getppid(), SIGQUIT);
//使用kill,用getppid获取父进程的进程号,并且给它发出SIGQUIT的信号,使它退出
}
return 0;
}
kill命令具体使用实例
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
~
这段代码结果就是死循环,完了我们杀死它执行:
kill + 信号或信号编号 + 进程号就可以结束进程
alarm函数
#include <stdio.h>
#include <unistd.h>
int main()
{
unsigned int sec;
//当执行到alarm之后,代码会接着往下执行,当设定的时间到后,会产生SIGALRM信号
sec = alarm(5);
printf("sec = %d\n", sec); //这里此前没有调用过alarm所以返回值就是0
sleep(3);
sec = alarm(6);
printf("sec = %d\n", sec