进程间通信

目录

一、管道

1.无名管道

1、特点:

2、原型:

3、应用(父子进程通信)

        ​编辑 

 思路:

2.FIFO 

1、特点

2、原型

3、应用

二、消息队列

1、特点

2、原型

3、应用

三、共享内存

1、特点

2、原型

3、例子

四、信号(signal)

1.信号概念

2.信号注册函数——入门版 

signal原型

3.信号注册函数——高级版

sigaction 的函数原型 

信号发送函数——高级版


进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

一、管道

1.无名管道

无名管道,是 UNIX 系统IPC最古老的形式。

1、特点:

  1. 它是半双工的(即数据只能单向流动),具有固定的读端和写端,数据读走就没有了。

  2. 它只能用于父子进程或者兄弟进程之间。

  3. 它一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数,它不属于其他任何文件系统,并且只存在于内存中。

2、原型:

#include <unistd.h>
int pipe(int pipefd[2]);
// 返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

format,png

         要关闭管道只需将这两个文件描述符关闭即可。

3、应用(父子进程通信)

        e519d179cfe14525a420bde43cdaf127.png 

 思路:

        1.建立管道(pipe)

        2.创建父子进程(fork)

        3.子进程:a.关闭写端(close),b.读进行操作(read),c.退出子进程(exit)

        4.父进程:  a.关闭读端(close),b.进程写操作(write),c.获取子进程退出的状态(wait)

#include <unistd.h>
#include "stdio.h"
#include "string.h"
#include <stdlib.h>
 //      int pipe(int pipefd[2]);
int main()
{
	int fd[2];
	int pid;
	char buf[128];

	//1.建立管道,判断是否成功
	if( pipe(fd) == -1){
		printf("creat pipe failed\n");
	}

	//2.创建父子进程
	pid = fork();

	if(pid < 0){	//判断是否成功创建父子进程
		printf("creat child failed\n");
	}
	else if(pid == 0){		//3.子进程
		printf("this is child,pid = %d\n",getpid());

		close(fd[1]);		//关闭写端
		read(fd[0],buf,128);//读操作
		printf("read from father:%s\n",buf);
		exit(0);			//退出子进程
	}
	else{					//4.父进程		
		printf("this is father,pid = %d\n",getppid());	
		close(fd[0]);		//关闭读端
		write(fd[1],"hello,wo is father",strlen("hello,wo is father"));//写操作
		wait();				//获取子进程退出状态
	}
	return 0;
}

上面代码是父进程写子进程读,那父进程读子进程写呢?

#include <unistd.h>
#include "stdio.h"
#include "string.h"
#include <stdlib.h>
 //      int pipe(int pipefd[2]);

int main()
{
	int fd[2];	
	int pid;
	char buf[128];

	if( pipe(fd) == -1){
		printf("creat pipe failed\n");
	}

	pid = fork();

	if(pid < 0){
		printf("creat child failed\n");
	}
	else if(pid > 0){
		sleep(1);    //休息一下,等子进程干完活了,在读操作,否则读不到东西
		printf("this is father\n");
		
		close(fd[1]);
		read(fd[0],buf,128);
		printf("father read:%s\n",buf);
		
	}else{
		printf("this is child\n");
		close(fd[0]);
		write(fd[1],"good",strlen("good"));
		printf("child write ok\n");
	}
	return 0;
}

2.FIFO 

FIFO,也称为命名管道,它是一种文件类型

1、特点

  1. FIFO可以在无关的进程之间交换数据,与无名管道不同。

  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

2、原型

1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);

mode 参数与open函数中的 mode 相同。创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它

  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);

int main()
{
    //创建FIFO
	if( mkfifo("./file",0600) == -1 && errno != EEXIST){
		printf("mkfifo failed\n");
		perror("why");
	}	
	return 0;
}

3、应用

FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。

readfifo.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);
#include <fcntl.h>

int main()
{
	char buf[30] = {0};

	int n_read = 0;

	//1.创建FIFO文件
	if( mkfifo("./file",0600) == -1 && errno != EEXIST){
		printf("mkfifo failed\n");
		perror("why");
	}

	//2.openFIFO文件
	int fd = open("./file",O_RDONLY);
	if(fd > 0){
		printf("read open success\n");
	}

	//3.读取文件内容
	while(1){
		n_read = read(fd,buf,30);

		printf("read %d byte from fifo,context:%s\n",n_read,buf);
	}

	//4.关闭文件
	close(fd);
	
	return 0;
}

writefifo.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main()
{
	int cnt = 0;
	char *str = "this is fifo.";
	
	
	int fd = open("./file",O_WRONLY);//只写打开
	printf("write  open success\n");

	while(1){
		write(fd,str,strlen(str));
		sleep(1);
		if(cnt == 5){
			break;
		}
	}	
	close(fd);
	return 0;
}

在两个终端里用 gcc 分别编译运行上面两个文件,可以看到输出结果如下:

CLC@Embed_Learn:~/IPC$ ./writefifo 
write  open success
CLC@Embed_Learn:~/IPC$ ./readfifo 
read open success
read 13 byte from fifo,context:this is fifo.
read 13 byte from fifo,context:this is fifo.
read 13 byte from fifo,context:this is fifo.
read 13 byte from fifo,context:this is fifo.
read 13 byte from fifo,context:this is fifo.
^C

二、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除

  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2、原型

 #include <sys/msg.h>
 // 创建或打开消息队列:成功返回队列ID,失败返回-1
 int msgget(key_t key, int flag);
 // 添加消息:成功返回0,失败返回-1
 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
 // 读取消息:成功返回消息数据的长度,失败返回-1
 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
 // 控制消息队列:成功返回0,失败返回-1
 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下两种情况下,msgget将创建一个新的消息队列:

        如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
        系统建立IPC通讯 (消息队列信号量共享内存) 时必须指定一个ID值(key)。通常情况下,该id值通过ftok函数得到。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok( const char * fname, int id );

        fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:

key_t key;

key = ftok(".", 1); //这样就是将fname设为当前目录。

        id是子序号。虽然是int类型,但是只使用8bits(1-255)。

        在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

查询文件索引节点号的方法是: ls -i ,还可以ls -ai.

        函数msgrcv在读取消息队列时,type参数有下面几种情况:

        type == 0,返回队列中的第一个消息;
        type > 0,返回队列中消息类型为 type 的第一个消息;
        type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
        可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
   //    int msgget(key_t key, int msgflg);

int main()
{
    //指定key值,创建消息队列
	if( msgget(0x6325,IPC_CREAT|0777) == -1 ){
		printf("msgget mkdir failuer\n");
	}else{
		printf("msgget mkdir ok\n");
	}	
	
	return 0;
}

3、应用

        服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。

msgwrite.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msgBuf{

	long msgtyp;
	char mtext[128];
};
int main()
{
    //1.指定消息队列的ID,ftok建立ID
	key_t key;
//key_t ftok(const char *pathname, int proj_id);        
    key = ftok(".",'z');
    printf("key = %x\n",key);
	
	struct msgBuf sendBuf = {888,"this is msg quen."};	

    //2.创建/打开队列
	int msgID = msgget(key,IPC_CREAT|0777);
	if( msgID == -1 ){
		printf("msgget mkdir failuer\n");
	}
	printf("msgget mkdir ok\n");

    //3.添加消息(参数:消息队列ID,添加消息地址,内容长度,默认为0)
	msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);	

	struct msgBuf readBuf;

    //4.读取消息(参数:消息队列ID,读取消息地址,读取消息长度,消息类型,默认为0)
	msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),988,0);

	printf("%s\n",readBuf.mtext);	

    //5.控制消息    IPC_RMID是将队列从内核中删除
	msgctl(msgID,IPC_RMID,NULL);
	return 0;
}

msgread.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msgBuf{

	long msgtyp;
	char mtext[128];
};

int main()
{
	struct msgBuf readBuf;	

	key_t key;
//key_t ftok(const char *pathname, int proj_id);	
	key = ftok(".",'z');
	printf("key =0x%x\n",key);

	int msgID = msgget( key, IPC_CREAT|0777 );
	if( msgID == -1 ){
		printf("msgget mkdir failuer\n");
	}
		printf("msgget read mkdir ok\n");

	int msgrcvLen = msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);

	printf("read %d byte,contine:%s\n",msgrcvLen,readBuf.mtext);

	struct msgBuf sendBuf = {988,"thank you for over."};
	msgsnd(msgID,&sendBuf,strlen(sendBuf.mtext),0);

    	msgctl(msgID,IPC_RMID,NULL);
	
	return 0;
}

三、共享内存

        共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

  2. 因为多个进程可以同时操作,所以需要进行同步。

  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。(防止两个或多个进程同时写数据,造成数据混乱)

2、原型

1 #include <sys/shm.h>
2  创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4  连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

3、例子

共享内存用来传递数据;

 shmwrite.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
	char *shmaddr;
	int shmid;//共享内存ID
	key_t key;//IPC键值
	key = ftok(".",1);//系统IPC键值的格式转换函数

	//1.创建共享内存(参数:键值,大小一般兆级1024,权限)
	shmid = shmget(key,1024*4,IPC_CREAT|0600);
	if(shmid != -1){
		printf("shmget ok,shmid = %d\n",shmid);
	}

	//2.映射,把共享内存挂载到进程
	shmaddr = shmat(shmid,0, 0);
	printf("shmat ok\n");

	//3.写/读数据
	strcpy(shmaddr,"chenlichen");
	sleep(5);
	printf("shm write ok\n");

	//4.释放共享内存
	shmdt(shmaddr);

	//5.干掉共享内存
	shmctl(shmid,IPC_RMID,0);
	return 0;
}

shmread.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//int shmget(key_t key, size_t size, int shmflg);
int main()
{
	int shmId;
	key_t key;
	char *shmaddr;

	key = ftok(".",1);
	
	shmId = shmget(key,1024*4,0);

	if(shmId != -1){
		printf("shmget succeed.\n");
		printf("shmId = %d\n",shmId);
	}

// void *shmat(int shmid, const void *shmaddr, int shmflg);
	shmaddr = shmat(shmId,0,0);	
	printf("shmaddr ok.\n");
	
	printf("shmread: %s\n",shmaddr);
	//int shmdt(const void *shmaddr);
	shmdt(shmaddr);

	
	return 0;
}

四、信号(signal)

        信号是软中断, Linux 处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

1.信号概念

信号的名字和编号:
        每个信号都有一个名字和编号,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

CLC@Embed_Learn:~/IPC$ kill -l
 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
16) SIGSTKFLT	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. 信号的处理有三种方法,分别是:忽略、捕捉和默认动作

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

        对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?

信号处理函数的注册
        信号处理函数的注册不只一种方法,分为入门版和高级版

        入门版:函数signal
        高级版:函数sigaction
信号处理发送函数
        信号发送函数也不止一个,同样分为入门版和高级版
        1.入门版:kill
        2.高级版:sigqueue

2.信号注册函数——入门版 

        处理中断都需要处理什么问题? 

         需要知道到底发生了什么信号。(有人敲门,想让我去开门)

        系统产生了一个信号,是由谁来响应?

        如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。 

signal原型

#include <signal.h>
//有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//signum 显然是信号的编号,handler 是中断函数的指针。

 简单一个信号注册的代码示例:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
	printf("get signalnum=%d\n",signum);
	printf("pid = %d\n",getpid());
	switch(signum){
		case 2:printf("SIGINT\n");break;
		case 9:printf("SIGKILL\n");break;
		case 10:puts("SIGUSR1");break;
	}
	printf("never quite\n");
}

int main()
{
	signal(SIGINT,handler);
	signal(SIGKILL,handler);
	signal(SIGUSR1,handler);
	while(1);
	return 0;
}

发送命令

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc,char **argv )	//传参
{
        int signum;
        int pid;
        signum = atoi(argv[1]);	//将argv[1]和argv[2]的asc码转化成整型数
        pid = atoi(argv[2]);

        printf("num = %d,pid = %d\n",signum,pid);

        kill(pid,signum);
        printf("send signal OK\n");

        return 0;
}

3.信号注册函数——高级版

        我们已经成功完成了信号的收发,那么为什么会有高级版出现呢?其实之前的信号存在一个问题就是,虽然发送和接收到了信号,可是总感觉少些什么,既然都已经把信号发送过去了,为何不能再携带一些数据呢?
我们需要另外的函数来通过信号传递的过程中,携带一些数据。咱么先来看看发送的函数吧。

sigaction 的函数原型 

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

这个函数的原版帮助信息,可以通过man sigaction来查看。

在函数原型中,第一个参数signum是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

在这里额外说一下struct sigaction结构体中的 sa_mask 成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。

sigaction 的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用 sigaction并改变动作之前都会一直有效。

关于结构体中的 flag 属性的详细配置,在此不做详细的说明了,只说明其中一点。如果设置为 SA_SIGINFO 属性时,说明了信号处理程序带有附加信息,也就是会调用 sa_sigaction 这个函数指针所指向的信号处理函数。否则,系统会默认使用 sa_handler 所指向的信号处理函数。在此,还要特别说明一下,sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。

关于void (*sa_sigaction)(int, siginfo_t *, void *);处理函数来说还需要有一些说明。void* 是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。

 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

那么,kill 函数发送的信号是无法携带数据的,我们现在还无法验证发送收的部分,那么,我们先来看看发送信号的高级用法后,我们再来看看如何通过信号来携带数据吧。

信号发送函数——高级版

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

 

使用这个函数之前,必须要有几个操作需要完成

  1. 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
  2. sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。

sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAc3Vuc2hpbWUu,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center

接收端

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

//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
/*
 struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

*/
//void     (*sa_sigaction)(int, siginfo_t *, void *);

void handler(int signum, siginfo_t *info,void *context)
{
      printf("get signum %d\n",signum);
      if(context != NULL){
              printf("get data = %d\n",info->si_int);
              printf("get data = %d\n",info->si_value.sival_int);
              printf("get pid = %d\n",info->si_pid);
      } 

}

int main()
{
       struct sigaction act;//信号处理结构体

       printf("pid = %d\n",getpid());//获取pid

       act.sa_flags = SA_SIGINFO;//配置flag,可读信息SA_SIGINFO
       act.sa_sigaction = handler;

       sigaction(SIGUSR1,&act,NULL);
       while(1);
       return 0;
}

 发送端

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

 /*
 int sigqueue(pid_t pid, int sig, const union sigval value);
 union sigval {
               int   sival_int;
               void *sival_ptr;
           };

*/

 int main(int argc,char **argv)
 {
       int signum;
       int pid;

       signum = atoi(argv[1]);
       pid = atoi(argv[2]);

       union sigval value;
       value.sival_int = 666;

       sigqueue(pid,signum,value);
       printf("pid %d,done\n",getpid());
       
       return 0;
 }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值