linux进程通讯--信号--管道

Linux 下的进程通信手段基本上是从 UNIX 平台上的进程通信手段继承而来的。而对 UNIX 发展做出重大贡献的两大主力 AT&T 的贝尔实验室及 BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。前者是对 UNIX 早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。
而 Linux 则把两者的优势都继承了下来UNIX 进程间通信(IPC)方式包括管道、FIFO 以及信号。System V 进程间通信(IPC)包括 System V 消息队列、System V 信号量以及 System V 共享内存区。Posix 进程间通信(IPC)包括 Posix 消息队列、Posix 信号量以及 Posix 共享内存区
在这里插入图片描述
linux 进程之间的通讯主要有下面几种:
1 管道 pipe 和命名管道:管道有亲缘关系进程间的通信,命名管道还允许无亲缘关系进程间通信
2 信号 signal:在软件层模拟中断机制,通知进程某事发生
3 消息队列:消息的链表包括 posix 消息队列和 SystemV 消息队列
4 共享内存:多个进程访问一块内存主要以同步
5 信号量:进程间同步
6 套接字 socket:不同机器间进程通信
下面是对它们的详解:
(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
(3)消息队列(Messge Queue):消息队列是消息的链接表,包括 Posix 消息队列和 SystemV 消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。
(4)共享内存(Shared memory):可以说是最有用的进程间通信方式,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
(5)信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。
(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,
应用非常广泛。


                                                信号

信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是硬件中断的软件模拟(软中断)。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程一个完整的信号生命周期
可以分为 3 个重要阶段,这 3 个阶段由 4 个重要事件来刻画的:信号产生、信号在进程中注册、信号在进程中注
销、执行信号处理函数

在这里插入图片描述
每个信号用一个整型常量宏表示,以 SIG 开头,比如 SIGCHLD、SIGINT 等,它们在系统头文件<signal.h>
中定义,也可以通过在 shell 下键入 kill –l 查看信号列表,或者键入 man 7 signal 查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自 3 个地方:
1)用户:用户能够通过输入 CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来
请求内核产生信号;
2) 内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出
等;
3)进程:一个进程可以通过系统调用 kill 给另一个进程发送信号,一个进程可以通过信号和另外一个进程
进行通信。
进程接收到信号以后,可以有如下 3 种选择进行处理:
4)接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下
CTRL+c,将导致内核向进程发送一个 SIGINT 的信号,进程如果不对该信号做特殊的处理,系统将采
用默认的方式处理该信号,即终止进程的执行;
5)忽略信号:进程可以通过代码,显示地忽略某个信号的处理;但是某些信号是不能被忽略的;
6)捕捉信号并处理:进程可以事先注册信号处理函数,当接收到某个信号时,由信号处理函数自动捕捉并
且处理该信号。
有两个信号既不能被忽略也不能被捕捉,它们是 SIGKILL 和 SIGSTOP。即进程接收到这两个信号后,只能
接受系统的默认处理,即终止进程。

1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1
36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5
40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9
44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5
60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1
64) SIGRTMAX
列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。

signal 信号处理机制
可以用函数 signal 注册一个信号处理函数。原型为:
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针 void (*)(int a)
sighandler_t signal(int signum, sighandler_t handler);
参数:signum  --- 表示要捕捉的信号
参数:handler  --- 表示要执行的动作
//捕捉终端 CTRL+c 产生的 SIGINT 信号:
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
void SignHandler(int iSignNo)
{
printf("Capture sign no:%d\n",iSignNo);
}
int main()
{
	signal(SIGINT,SignHandler);//注册信号处理函数
	while(1)
	sleep(1);
	return 0;
}

4.2.3 kill 和 raise 信号发送函数
原型为:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
#include <signal.h>
int raise(int sig)
kill 函数向指定进程发送指定信号。raise 函数则允许进程向自身发送信号。
参数 pid 为将要接受信号的进程的 pid,可以通过 getpid()函数获得
参数 sig 为要发送的信号
如果成功,返回 0,否则为-1

kill的小实例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1){
if(getchar()==EOF) //运行之后输入没有反应,当按下 Ctrl+d(EOF),进程关闭
kill(getpid(),SIGQUIT);
}
return 0;
}
//raise的小实例
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
/*向自己发送 SIGQUIT 命令*/
raise(SIGQUIT);
while(1);
return 0;
}
//睡眠函数
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
函数 sleep 让进程睡眠 seconds 秒,函数 usleep 让进程睡眠 usec 微秒。
sleep 睡眠函数内部是用信号机制进行处理的,用到的函数有:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//告知自身进程,要进程在 seconds 秒后自动产生一个 SIGALRM 的信号
int pause(void); //将自身进程挂起,直到有信号发生时才从 pause 返回
示例:模拟睡眠 3 秒:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void SignHandler(int iSignNo)
{
printf("signal:%d\n",iSignNo);
}
int main()
{
signal(SIGALRM,SignHandler);
alarm(3); //等待 3 秒之后自动产生 SIGALRM 信号
printf("Before pause().\n");
pause(); //将进程挂起,直到有信号发生才退出挂起状态
printf("After pause().\n");
return 0;
}
注意:因为 sleep 在内部是用 alarm 实现的,所以在程序中最好不要 sleep 与 alarm 混用,以免造成混乱。

                                             管道

1、无名管道(PIPE)
无名管道的特点:
1)只能在亲缘关系进程间通信(父子或兄弟)
2) 半双工(固定的读端和固定的写端)
3 )他是特殊的文件,可以用 read、write 等,只能在内存中
管道函数原型:
#include <unistd.h>
int pipe(int fds[2]);
管道在程序中用一对文件描述符表示,其中一个文件描述符有可读属性,一个有可写的属性。fds[0]是读,fds[1]
是写。
函数 pipe 用于创建一个无名管道,如果成功,fds[0]存放可读的文件描述符,fds[1]存放可写文件描述符,并且函
数返回 0,否则返回-1。

在Linux中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:
限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题。
注意:从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据。
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

void SignHandler(int iSignNo)
{
	printf("signal:%d\n",iSignNo);
}
int main(void)
{
	int fd[2]={0};
	pipe(fd);
	char str[10]={0};
	signal(SIGPIPE,SignHandler);
	if(fork()==0){
		close(fd[1]); 
		printf("wait pa write\n");
		//先进行关闭写,是因为无名管道是单向的,要么读,要么写,
		//所以过来来说得先进行关闭写操作
		
		if(read(fd[0],str,10)>0){//这个读操作会一直等待,知道有数据写进管道为止
			printf("Child read %s\n",str);
		}else{
			printf("Child no read\n");	
		}
		close(fd[0]);
		exit(0);
	}else{
		sleep(1);
		close(fd[0]);
		write(fd[1], "ljg666", 6);
		wait();
		close(fd[1]);
		exit(0);		
	}
	return 0;
}

注:管道两端的关闭是有先后顺序的,如果先关闭写端则从另一端读数据时,read 函数将返回 0,表示管道已经关闭;但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到 SIGPIPE 信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的 write 函数返回一个负值,表示管道已经关闭

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>

void SignHandler(int iSignNo)
{
	printf("signal:%d\n",iSignNo);
}
int main(void)
{
	int fd[2]={0};
	pipe(fd);
	char str[10]={0};
	signal(SIGPIPE,SignHandler);
	if(fork()==0){
		close(fd[1]); 
		close(fd[0]);
		exit(0);
	}else{
		sleep(1);
		close(fd[0]);
		write(fd[1], "ljg666", 6);
		wait();
		close(fd[1]);
		exit(0);		
	}
	return 0;
}

2、有名管道(FIFO)
无名管道只能在亲缘关系的进程间通信大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的形式实现不相关进程间的通信

1)创建有名管道:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数 pathname 为要创建的 FIFO 文件的全路径名;
参数 mode 为文件访问权限
如果创建成功,则返回 0,否则-1

2)删除 FIFO 文件的函数原型为:

#include <unistd.h>
int unlink(const char *pathname);

注:
对 FIFO 类型的文件的打开/关闭跟普通文件一样,都是使用 open 和 close 函数。如果打开时使用 O_WRONLY选项,则打开 FIFO 的写入端,如果使用 O_RDONLY 选项,则打开 FIFO 的读取端,写入端和读取端都可以被几个进程同时打开。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO 是严格地遵循先进先出规则的,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如 lseek()等文件定位操作,必须按照从前到后的顺序依次读写。如果以读取方式打开 FIFO,并且还没有其它进程以写入方式打开 FIFO,open 函数将被阻塞;同样,如果以写入方式打开 FIFO,并且还没其它进程以读取方式 FIFO,open 函数也将被阻塞。但是,如果 open 函数中包含O_NONBLOCK 选项,则上述两种情况下调用 open 都不被阻塞。与 PIPE 相同,关闭 FIFO 时,如果先关读取端,将导致继续往 FIFO 中写数据的进程接收 SIG_PIPE 的信号。

小示例:

然后 vi write.c 如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fdFifo = open("MyFifo.pip",O_WRONLY); //1. 打开(判断是否成功打开略)
write(fdFifo, "hello", 6); //2. 写
close(fdFifo); //3. 关闭
return 0;
}
然后 vi read.c 如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
char szBuf[128];
int fdFifo = open("MyFifo.pip",O_RDONLY); //1. 打开
if(read(fdFifo,szBuf,sizeof(szBuf)) > 0) //2. 读
puts(szBuf);
close(fdFifo); //3. 关闭
return 0;
}

然后gcc –o write write.c
gcc –o read read.c
./write //发现阻塞,要等待执行./read
./read
在屏幕上输出 hello

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值