进程间通信功能
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程 希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
Linux操作系统支持的主要进程间通信的通信机制
进程间通信的实质
系统只要创建一个进程,就会给当前进程分配4G的虚拟内存(32位操作系统),虚
拟内存 不是常说的内存条的空间,内存条的空间称之为物理内存,虚拟内存和物理
内存之间存在映 射关系 4G的虚拟内存分为3G的用户空间(0~3G)和1G(3~4G)
的内核空间, 用户空间是进程所私有的,每一个进程的用户空间只能自己访问和使用,我们之前说的栈区、堆区、数据区、代码区等都是用户空间的区域.内核空间是所有进程
所公有的,也就意味着绝大多数进程间通信方式,本质就是对内核空间的操作。
特殊的进程间通信方式
socket通信可以实现不同主机的进程间通信,其他六个只能在一台主机的多个进程
间通信,信号通信是唯一的一种异步通信机制 共享内存是所有进程间通信方式中效
率最高的,他是直接对物理内存进行操作。
信号
信号的概念
信号是软件中断,它是在软件层次上对中断机制的一种模拟。
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个 突发事件。 信号是一种异步通信方式。
每个信号的名字都以字符SIG开头。
每个信号和一个数字编码相对应,在头文件signum.h中,这些信号都被定义为正整数。
在Linux下,要想查看这些信号和编码的对应关系,可使用命令:kill -l。
信号是由当前系统已经定义好的一些标识,每一个标识都会在特定的场合使用,并且都会对进程有一定的影响。
当信号产生时,会让当前信号做出相应的操作这些信号都是已经定义好的,我们不能自己再去创造,直接使用这些就可以。
产生信号的方式
1、当用户按某些终端键时,将产生信号
例如:终端上按“Ctrl+c”组合键通常产生中断信号SIGINT、
终端上按"Ctrl+\"键通常产生中断信号SIGQUIT、
终端上按"Ctrl+z"键通常产生中断信号SIGSTOP。
2、硬件异常将产生信号 除数为0,无效的内存访问等。这些情况通常由硬件检测到
,并通知内核,然后内核产生 适当的信号发送给相应的进程。
3、软件异常将产生信号。 当检测到某种软件条件已发生,并将其通知有关进程时,
产生信号。
4、调用kill函数将发送信号。 注意:接收信号进程和发送信号进程的所有者必须相
同,或发送信号进程的所有者必 须是超级用户。
5、运行kill命令将发送信号。 此程序实际上是使用kill函数来发送信号。也常用此命
令终止一个失控的后台进程。
信号的默认(缺省)处理方式
当进程中产生了一个信号,就会让当前进程做出一定的反应, 默认处理进程的方式
如下
1、终止进程:当信号产生后,当前进程就会立即结束
2、缺省处理:当信号产生后,当前进程不做任何处理
3、停止进程:当信号产生后,使得当前进程停止
4、让停止的进程回复运行:当信号产生后,停止的进程会回复执行(后台进程)
进程接收到信号后的处理方式
1、执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2、忽略此信号 接收到此信号后没有任何动作。
3、执行自定义信号处理函数 用用户定义的信号处理函数处理该信号。
注意:SIGKILL和SIGSTOP这两个信号只能以默认的处理方式执行,不能忽略也不
能自定义。
kill函数
例子
使用kill给父进程发送信号,然后父进程接收到信号后直接退出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
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(), SIGINT);
}
return 0;
}
alarm函数
例子
如果一个程序中出现多个alarm闹钟,第一个如果没有到达指定的时间就遇到第二个。则第一个的闹钟时间清除,按照第二个alarm闹钟的时间继续向下运行。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
unsigned int sec;
//当执行到alarm之后,代码会接着往下执行,当设定的时间到后,会产生SIGALRM信号
//如果alarm之前没有设置其他闹钟,则返回0,如果之前设置了,则返回之前剩余的秒数
//如果一个程序中出现多个alarm闹钟,第一个如果没有到达指定的时间就遇到第二个
//则第一个的闹钟时间清除,按照第二个alarm闹钟的时间继续向下运行
sec = alarm(5);
printf("sec = %d\n", sec);
sleep(3);
sec = alarm(6);
printf("sec = %d\n", sec);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
raise函数
例子
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int num = 0;
while(1)
{
printf("hello world\n");
sleep(1);
num++;
//当循环执行5秒后,进程退出
if(num == 5)
{
//使用raise给当前进程本身发送信号
raise(SIGALRM);
//kill(getpid(), SIGALRM);
}
}
return 0;
}
abort函数
注意:即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止, 且在终止前会刷新缓冲区,关闭文件描述符。
例子
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
int num = 0;
while(1)
{
printf("hello world\n");
sleep(1);
num++;
//当循环执行5秒后,进程退出
if(num == 5)
{
abort();
}
}
return 0;
}
pause函数
例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent peocess\n");
//使用pause阻塞等待捕捉信号
pause();
}
else //子进程的代码区
{
printf("This is son process\n");
sleep(3);
kill(getppid(), SIGINT);
}
return 0;
}
signal函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void handler(int sig);
int main(int argc, char const *argv[])
{
//以默认的方式处理信号
#if 0
if(signal(SIGINT, SIG_DFL) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGTSTP, SIG_DFL) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
#endif
//以忽略的方式来处理信号
#if 0
if(signal(SIGINT, SIG_IGN) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGQUIT, SIG_IGN) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGTSTP, SIG_IGN) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
//注意:SIGKILL和SIGSTOP这两个信号只能以默认的方式处理,不能忽略或者捕捉
// if(signal(SIGKILL, SIG_IGN) == SIG_ERR)
// {
// perror("fail to signal");
// exit(1);
// }
#endif
//以用户自定义方式处理信号
#if 1
if(signal(SIGINT, handler) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGQUIT, handler) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
if(signal(SIGTSTP, handler) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
#endif
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
void handler(int sig)
{
if(sig == SIGINT)
{
printf("SIGINT正在处理\n");
}
if(sig == SIGQUIT)
{
printf("SIGQUIT正在处理\n");
}
if(sig == SIGTSTP)
{
printf("SIGTSTP正在处理\n");
}
}
查看pid号 ps -aux|grep a.out
Kill -9 加上进程ID号 用来杀死进程。
signal函数的返回值
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void *ret_handler;
void handler(int sig)
{
printf("**********************\n");
printf("nihao beijing\n");
printf("welcome to 1000phone\n");
printf("**********************\n");
if(signal(SIGINT, ret_handler) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
}
int main(int argc, char const *argv[])
{
if((ret_handler = signal(SIGINT, handler)) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
可重入函数
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误 可重入函数就是可以被中断的函数,当前函数可以在任何时刻中断它,并执行另一块代码, 当执行完毕后,回到原本的代码还可以正常继续运行。
编写可重入函数:
1、不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
2、不调用动态内存分配、释放的函数。
3、不调用任何不可重入的函数(如标准I/O函数)。
注:
即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为信号处理过程中,errno值随 时可能被改变。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
void handler(int sig)
{
printf("SIGINT\n");
}
int main(int argc, char *argv[])
{
signal(SIGINT, handler);
//案例1:
#if 0
//sleep是一个可重入函数,但是当执行信号处理函数之后,不会回到原本的位置继续睡眠
//sleep(10);
//alarm函数是一个可重入函数,当他执行时,如果有信号产生并执行信号处理函数,执行完毕后,会继续运行
alarm(10);
while(1)
{
printf("hello world\n");
sleep(1);
}
#endif
//案例2:
#if 1
char buf[32] = "";
//read也是一个可重入函数,在等待终端输入时,如果产生信号并执行信号处理函数,信号处理
//函数执行完毕后,可以继续输入数据,read可以读取到信号处理函数之后的数据
if(read(0, buf, 20) == -1)
{
perror("fail to read");
exit(1);
}
printf("buf = [%s]\n", buf);
#endif
return 0;
}