常见的通信方式

无名管道通信pipe

无名管道是一种半双工的通信方式,数据只能单方向流动,通信的时候只能在具有亲缘关系的进程间通信,也就是上下级进程

命名管道通信fifo

命名管道是一种半双工的通信方式,但是数据读写具有原子性,优势在于可以用于任意进程之间通信

消息队列MessageQueue

消息队列就是由消息组成的链式队列,存放在内核中并由消息队列标识符进行标识,消息队列克服了消息量少,管道只能承受无格式字节流数据传输以及缓冲区大小受限的缺点

共享内存SharedMemory

共享内存就是映射一块其他的进程都可以访问的内存空间,该段内存由多个进程同时读取数据。共享内存属于最快IPC通信方式,由于通信机制效率最高,那么多个进程同时访问的时候,需要通过信号量互斥进行同步管理

信号量Semaphore

信号量是一个计数器,可以用来控制多个进程对于共享资源的访问,它作为一个锁机制存在,防止某个进程在访问共享资源的时候,影响到其他进程访问该资源,作为进程之间访问共享资源协调手段

信号signal

信号是一种复杂的通信机制,主要利用操作系统的消息机制,通过不同的信号值表示不同的信号内容,从而引起进程执行不同的内容

套接字socket

套接字也是进程当中的一种通信方式,不同的是该种机制用于不同计算机之间的通信,通过套接字的形式完成数据收发

管道通信方式

管道概念:是指无名管道和有名管道,实际上套接字和管道的方式是类似,套接字用于不同计算机之间的通信,管道只能用于同一个计算机之间的进程通信。链接两个进程对象

管道的局限性:

  1. 数据不能进程自己读自己写
  2. 管道中数据不能反复读取,一旦读取完成,则管道数据消失
  3. 管道中采用半双工通信,数据只能单方向流动,不具备原子性写入

无名管道

NAME   创建无名管道
       pipe - create pipe
SYNOPSIS
//头文件
       #include <unistd.h>
       函数原型:
       int pipe(int pipefd[2]);
       函数参数:
	    int pipefd[2] 该数组表示无名管道拥有两个文件描述符
		pipefd[0] refers to the read end of the pipe.  读端
		pipefd[1] refers to the write end of the pipe.  写端
		返回值:成功  为0     失败  -1

int main()
{
	//定义读端和写端文件描述符数组
	int fd[2];
	//创建无名管道
	int ret=pipe(fd);
	if(ret == -1)
	{
		perror("PIPE falied\n");
		return -1;
	}
	
	//定义存储缓冲区
	char buf[50];
	//建立亲缘进程
	pid_t x=fork();//创建父子进程
	if(x >0)//父进程  读取数据 读端 fd[0]
	{
		printf("我是父进程:PID=%d\n",getpid());
		//无名管道 读端 
		while(1)
		{
			//清空缓冲区
			memset(buf,0,sizeof(buf));
			//读取数据
			read(fd[0],buf,sizeof(buf));
			printf("接受子进程的内容为:%s\n",buf);
			//退出
			if(!strcmp(buf,"bye"))
			{
				break;//退出循环
			}
		}
		wait(NULL);
	}
	if(x == 0)//子进程 写入数据 写端 fd[1]
	{
		printf("我是子进程:PID=%d\n",getpid());
		//无名管道 写端
		while(1)
		{
			//清空缓冲区
			memset(buf,0,sizeof(buf));
			//写入数据
			printf("请输入你想对父进程说的话\n");
			scanf("%s",buf);
			write(fd[1],buf,strlen(buf));
			//退出
			if(!strcmp(buf,"bye"))
			{
				break;//退出循环
			}
		}
		exit(0);//退出
	}	
	return 0;
}

无名管道没有文件名,只有读端和写端文件描述符,不能使用lseek定位
无名管道采用半双工通信,只能用于亲缘间通信
无名管道若是写入数据,则读端会一直阻塞,直到有数据写入为止
无名管道写入数据时进程死亡,则数据一直保存在管道中
无名管道写入数据没有原子性保存,注意缓冲区大小稳定操作
无名管道操作较为简单,适用场景较为单一,性能较弱限制条件多
查看无名管道的个数“ulimit -a

有名管道

创建

NAME  生成一个有名管道文件【mkfifo既是函数名又是shell命令】
       mkfifo - make a FIFO special file (a named pipe)
SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       函数原型:
       int mkfifo(const char *pathname, mode_t mode);
       函数参数:
	   const char *pathname, 有名管道的存储路径
	   mode_t mode           有名管道的权限 八进制  0777
       返回值:成功  返回为0        失败 返回-1

删除

NAME 删除有名称的文件对象【unlink既是函数名又是shell命令】
       unlink- delete a name and possibly the file it refers to
SYNOPSIS
       #include <unistd.h>
       函数原型:
       int unlink(const char *pathname);
       函数参数:
	   const char *pathname, 提供需要删除文件的路径
	   返回值:成功  返回为0        失败 返回-1

在这里插入图片描述

案例

#include "myhead.h"
#define FIFO "/home/softeem/fifo"  //宏定义管道名

//建立有名管道文件
int create_FIFO(const char *pathname)
{
	//判断管道文件是否存在
	int ret=access(pathname,F_OK);
	//管道不存在就创建管道
	if(ret == -1)
	{
		//创建管道文件
		ret=mkfifo(pathname,0666);//可读可写
		if(ret == -1)
		{
			perror("fifo failed\n");
			return -1;
		}
		else
			return 0;
	}
	return 0;
}

int main()
{
	//建立有名管道文件
    int ret=create_FIFO(FIFO);
	if(ret == -1)//操作失败 结束运行
		return -1;
	//打开文件
	int fd=open(FIFO,O_RDWR);
	if(fd == -1)//打开文件失败
	{
		perror("open failed\n");
		return -1;
	}
	//准备缓冲区
	char buf[50];
	//进程send 负责发送
	while(1)
	{
		//清空缓冲区
		memset(buf,0,sizeof(buf));
		//写入数据
		printf("请输入你想对进程说的话\n");
		scanf("%s",buf);
		write(fd,buf,strlen(buf));
		//退出
		if(!strcmp(buf,"bye"))
			break;//退出循环
	}
	//关闭文件
	close(fd);
	return 0;
}

接收端

#include "myhead.h"
#define FIFO "/home/softeem/fifo"

//建立有名管道文件
int create_FIFO(const char *pathname)
{
	//判断管道文件是否存在
	int ret=access(pathname,F_OK);
	if(ret == -1)//文件不存在
	{
		//创建管道文件
		ret=mkfifo(pathname,0666);//可读可写
		if(ret == -1)
		{
			perror("fifo failed\n");
			return -1;
		}
		else
			return 0;
	}
	return 0;
}

int main()
{
	//建立有名管道文件
    int ret=create_FIFO(FIFO);
	if(ret == -1)//操作失败 结束运行
		return -1;
	//打开文件
	int fd=open(FIFO,O_RDWR);
	if(fd == -1)//打开文件失败
	{
		perror("open failed\n");
		return -1;
	}
	//准备缓冲区
	char buf[50];
	//进程recv 负责接受
	while(1)
	{
		//清空缓冲区
		memset(buf,0,sizeof(buf));
		//读取数据
		read(fd,buf,sizeof(buf));
		printf("接受的内容为:%s\n",buf);
		//退出
		if(!strcmp(buf,"bye"))
			break;//退出循环
	}
	//关闭文件
	close(fd);
	//删除文件
	unlink(FIFO);
	return 0;
}

有名管道特点

  1. 有名管道可以用于任意两个进程间通信
  2. 有名管道属于一种文件类型,可以通过系统IO获取文件描述符
  3. 有名管道拥有文件权限,同时可以使用read/write函数读写数据
  4. 有名管道虽然是文件类型数据,但是不能使用lseek定位数据
  5. 有名管道具有写入原子性,支持多个进程同时写入数据,且数据不会错乱
  6. 有名管道属于先进先出的状态形式,最先写入的数据最先读取

管道检测原子性

管道检测原子性:指一个操作要么完全执行,要么完全不执行,不会处于中间状态。在多线程或多进程环境中,原子操作可以防止多个进程同时对共享资源进行读写,从而避免数据竞争和不一致性。

NAME   管理文件描述符对象
       fcntl - manipulate file descriptor
SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>
       函数原型:
       int fcntl(int fd, int cmd, ... /* arg */ );
       函数参数:
	   int fd, 需要管理的文件描述符对象
	   int cmd, 对文件描述符属性进行管理宏定义
	     F_GETFD (void) 读取文件描述符的属性
         F_SETFD (int)  设置文件描述符的属性
         F_GETFL (void) 获取存在的文件属性        
         F_SETFL (int)  设置文件的操作属性
	   ... /* arg */命令字所需要的参数值【读取无需参数,设置需要参数】
	   文件属性:O_RDONLY, O_WRONLY, O_RDWR
	            O_CREAT,  O_EXCL, O_NOCTTY, O_TRUNC
				O_APPEND 追加属性,  O_ASYNC 信号异步,
                O_DIRECT 目录属性,  O_NOATIME 当前时间, 
				O_NONBLOCK 非阻塞属性 

无名管道原子性

#include "myhead.h"

int main()
{
	//创建无名管道
	int fd[2];//fd[0]读端 fd[1]写端
	int ret=pipe(fd);//创建无名管道
	if(ret == -1)
    {
		perror("pipe failed\n");
		return -1;
	}
	
	//无名管道用于亲缘间通信
	pid_t x=fork();
	if(x > 0)//父进程
	{
		printf("父进程 pid=%d\n",getpid());
		close(fd[0]);//关闭读端
		//保证写入的持续性,将写端描述符设置为非阻塞
		int status=fcntl(fd[1],F_GETFL);//获取属性
		status |= O_NONBLOCK;//非阻塞属性
		fcntl(fd[1],F_SETFL,status);//设置属性
		//持续写入
		int num=0;
		while(1)
		{
			int n=write(fd[1],"A",1);//写入数据
			if(n == -1)
				break;
			else//统计成功写入的次数
				printf("num=%d\n",num++);
		}
		wait(NULL);
	}
	if(x == 0)//子进程
	{
		printf("子进程 pid=%d\n",getpid());
		close(fd[1]);//关闭写端
		exit(0);
	}
	return 0;
}

有名管道原子性

#include "myhead.h"
#define FIFO "/home/softeem/fifo"

//建立有名管道文件
int create_FIFO(const char *pathname)
{
	//判断管道文件是否存在
	int ret=access(pathname,F_OK);
	if(ret == -1)//文件不存在
	{
		//创建管道文件
		ret=mkfifo(pathname,0666);//可读可写
		if(ret == -1)
		{
			perror("fifo failed\n");
			return -1;
		}
		else
			return 0;
	}
	return 0;
}

int main()
{
	//建立有名管道文件
    int ret=create_FIFO(FIFO);
	if(ret == -1)//操作失败 结束运行
		return -1;
	//打开文件
	int fd=open(FIFO,O_RDWR);
	if(fd == -1)//打开文件失败
	{
		perror("open failed\n");
		return -1;
	}
	//设置文件描述符为非阻塞
	int status=fcntl(fd,F_GETFL);//获取属性
	status |= O_NONBLOCK;//非阻塞属性
	fcntl(fd,F_SETFL,status);//设置属性
	//持续写入
	int num=0;
	while(1)
	{
		int n=write(fd,"A",1);//写入数据
		if(n == -1)
			break;
		else//统计成功写入的次数
			printf("num=%d\n",num++);
	}
	//关闭文件
	close(fd);
	return 0;
}

IPC通信方式

IPC通信概念

共享内存、消息队列以及信号量统称为IPC通信方式,又称为systemV(V罗马数字5),在系统中他们使用键值作为唯一标识符,而且这些资源都是持续性资源,即创建之后不会因为进程运行结束而消失,除非调用特殊的函数或者命令执行删除,比如kill -9
systemV成为IPC通信在linux环境中存在三个形式,分别是共享内存、消息队列和信号量,用于同一个主机间的两个进程的消息传递或者消息同步,可以通过ipcs命令查看系统所有的ipc对象,可以通过命令获取ipc对象的键值和对象id

在这里插入图片描述

key键值获取

第一种:可以任意提供整数值作为键值,缺点在于IPC一旦创建则键值生效
第二种:通过系统函数ftok获取系统键值,由系统生成唯一键值对象

NAME   通过项目路径和项目编号生成IPC对象的系统键值key
       ftok  -  convert  a pathname and a project identifier to a System V IPC key
头文件
       #include <sys/types.h>
       #include <sys/ipc.h>
       函数原型:
       key_t ftok(const char *pathname, int proj_id);
       函数参数:
	   const char *pathname, 提供存在的项目路径
	   int proj_id           提供任意的正整数
	   返回值:
	      成功 返回一个系统未使用的键值key
		  失败 返回-1

案例
#include "myhead.h"
int main()
{
	//生成系统键值
	key_t key=ftok("./",10);
	printf("key:%d\n",key);
	return 0;
}

在这里插入图片描述

注意点:1.两个进程间的ftok函数的参数必须相同,那么才能产生同一个键值
2.ftok函数的第一参数一般使用项目所在路径,可以选择当前路径作为参数
3.同一个目录中多个进程使用不同的ipc对象,若需要区分key键值,则通过第二参数正整数改变key的内容

消息队列

消息队列是提供一种携带数据标识的特殊管道
消息队列查询方式:ipcs -q
消息队列删除方式:ipcrm -q
消息队列ID: ipcrm -Q key键值

消息队列特点

  • 消息从消息队列读取之后,队列中的数据消息消失,将从队列中删除处理
  • 消息队列当中,若存在未被读取的数据,则会残留消息队列中积累
  • 在读取消息的时候,需要通过表示选择消息数据包接受
  • 在消息队列中,多个消息表示相同的情况下,则遵循先进先出的原则
 struct msgbuf 
 {
		   long mtype;  消息类型  /* message type, must be > 0 */
		   char mtext[1];  消息内容  /* message data */
}; 注意该结构体需要自行定义,消息内容大小由自身需求决定

消息队列API

消息队列是一种以msg开头的系列函数

NAME  获取systemV中消息队列的id
       msgget - get a System V message queue identifier
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       函数原型:
       int msgget(key_t key, int msgflg);
       函数参数:
	     key_t key, 需要提供系统键值,使用ftok函数获取键值
		 int msgflg 消息队列的权限
		  IPC_CREAT 创建权限,若IPC不存在则创建对象
		  IPC_EXCL  判断IPC对象若存在,则退出创建操作
		            此时IPC对象的错误码为errno==EEXIST
		  mode 提供消息队列的操作权限,八进制权限(例如0666)
		返回值: 成功 返回消息队列ID 失败 -1  

消息队列发送与接受

NAME  消息队列的发送和接受
       msgrcv, msgsnd - System V message queue operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       函数原型:
       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 消息发送
       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);消息接受
       函数参数:
	   int msqid, 消息队列ID msgget函数返回值
	   void *msgp, 存放发送或者接受消息的缓冲区(结构体)
	    struct msgbuf 
         {
		   long mtype;  消息类型 
		   char mtext[1];  消息内容  
         }; 注意该结构体需要自行定义,消息内容大小由自身需求决定
	   size_t msgsz, 发送或者接受的大小
	               【发送计算文本大小,接受获取整个大小】
	   long msgtyp,接受函数中选择接受标识
	   int msgflg 默认设置为0
	    返回值:   
		   成功    发送函数为0       接受函数获取接受字节数
		   失败    返回为-1

消息队列属性

NAME  消息队列属性管理
       msgctl - System V message control operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       函数原型:
       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
       函数参数:
	   int msqid, 消息队列的id
	   int cmd,   消息队列的控制命令字
	     IPC_STAT 获取消息的属性,内存保存在 struct msqid_ds
         IPC_SET 设置消息的属性,通过属性结构体struct ipc_perm
         IPC_RMID 删除消息队列,第三参数设置为NULL 
	   struct msqid_ds *buf
	    struct msqid_ds {
               struct ipc_perm msg_perm; 属性结构体
               time_t          msg_stime;上次发送消息的时间
               time_t          msg_rtime;上次接受消息的时间
               time_t          msg_ctime; 上次修改数据时间
               unsigned long   __msg_cbytes;选中消息长度
               msgqnum_t       msg_qnum; 消息的个数               
               msglen_t        msg_qbytes; 获取消息最大尺寸
			   pid_t           msg_lspid; 发送消息进程号
               pid_t           msg_lrpid; 接受消息的进程号
           };
        struct ipc_perm {
               key_t          __key;消息队列键值
               uid_t          uid;用户对象id
               gid_t          gid;同组用户id
               uid_t          cuid;创建者id
               gid_t          cgid;创建组id
               unsigned short mode;消息队列权限
               unsigned short __seq; 序列号
           };

消息队列通信步骤
操作步骤:

  • 申请消息队列所需系统键值key ftok()
  • 通过系统键值key获取消息队列ID msgget()
  • 通过结构体配置消息标识和内容,选择发送或者接收==msgsend()/msgrcv()
  • 获取消息队列的属性或者删除消息队列 msgctl() IPC_STAT IPC_RMID
#include "myhead.h"

//定义携带标识的结构体
struct msgbuf
{
	long mtype;//消息标识
	char mtext[100];//消息内容
};

int main()
{
	//1.获取系统键值
	key_t key=ftok(".",10);
	if(key == -1)
	{
		perror("key failed\n");
		return -1;
	}
	//2.获取消息队列ID
	int msqid=msgget(key,IPC_CREAT|IPC_EXCL|0666);
	//若获取消息队列ID时,消息队列key已存在则直接获取
	if(msqid == -1&&errno == EEXIST)
	{
		msqid=msgget(key,0666);//直接获取存在消息队列ID
	}
	else if(msqid == -1)//创建失败
	{
		perror("msgget failed\n");
		return -1;
	}
	//3.发送消息
	struct msgbuf buf;//实例化结构体
	buf.mtype=666;//消息标识
	printf("请输入消息内容\n");
	scanf("%s",buf.mtext);//消息内容
	msgsnd(msqid,&buf,strlen(buf.mtext),0);
	//4.获取消息队列属性
	struct msqid_ds ds;//属性结构体
	msgctl(msqid,IPC_STAT,&ds);//获取消息队列属性
	printf("消息的尺寸:%ld\n",ds.__msg_cbytes);
	printf("消息发送进程号:%d\n",ds.msg_lspid);
	printf("消息队列键值:%d\n",ds.msg_perm.__key);
	return 0;
}

发送

#include "myhead.h"

//定义携带标识的结构体
struct msgbuf
{
	long mtype;//消息标识
	char mtext[100];//消息内容
};

int main()
{
	//1.获取系统键值
	key_t key=ftok(".",10);
	if(key == -1)
	{
		perror("key failed\n");
		return -1;
	}
	//2.获取消息队列ID
	int msqid=msgget(key,IPC_CREAT|IPC_EXCL|0666);
	//若获取消息队列ID时,消息队列key已存在则直接获取
	if(msqid == -1&&errno == EEXIST)
	{
		msqid=msgget(key,0666);//直接获取存在消息队列ID
	}
	else if(msqid == -1)//创建失败
	{
		perror("msgget failed\n");
		return -1;
	}
	//3.接受消息
	struct msgbuf buf;//实例化结构体
	buf.mtype=666;//消息标识
	ssize_t n=msgrcv(msqid,&buf,sizeof(buf),buf.mtype,0);//接受
	printf("接受%ld字节 内容为%s\n",n,buf.mtext);
	//4.获取消息队列属性
	struct msqid_ds ds;//属性结构体
	msgctl(msqid,IPC_STAT,&ds);//获取消息队列属性
	printf("消息的尺寸:%ld\n",ds.__msg_cbytes);
	printf("消息发送进程号:%d\n",ds.msg_lspid);
	printf("消息队列键值:%d\n",ds.msg_perm.__key);
	return 0;
}

IPC之共享内存

通信过程中只需要获取共享内存操作首地址,然后通过首地址进行数据的写入和读取即可。

共享内存查询

查看IPC共享内存方式:ipcs -m
删除IPC共享内存方式:ipcrm -m
共享内存ID ipcrm -M 键值key

  • 共享内存只有等待的进程链接数为0的时候,才能真正删除共享内存,RMID只是为了删除提前做准备
  • 共享内存只能通过可读可写的方式进行映射,那么才能实时读写数据
  • 共享内存映射过程中,若不设置共享内存操作地址,由系统进行闲时分配共享内存操作地址,此时代码更具备可移植性
  • 共享内存中的数据读取之后不会刷新,只有重写覆盖才能更新数据内容,也就是读取过程中若不限制,则会导致反复读取

共享内存API

共享内存是一种以shm开头的系列函数

NAME   共享内存的id获取
       shmget - allocates a System V shared memory segment
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>
       函数原型
       int shmget(key_t key, size_t size, int shmflg);
       函数参数:
	   key_t key,  共享内存的需求键值  通过ftok函数获取
	   size_t size, 需要申请的共享内存的大小(单位字节)
	   int shmflg  共享内存的标识权限
	      IPC_CREAT 创建权限,若IPC不存在则创建对象
		  IPC_EXCL  判断IPC对象若存在,则退出创建操作
		            此时IPC对象的错误码为errno==EEXIST
		  mode 提供消息队列的操作权限,八进制权限(例如0666)
	   返回值 成功  返回共享内存的id号      失败 -1

共享内存的映射和取消

NAME   共享内存映射和取消
       shmat, shmdt - System V shared memory operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>
       函数原型
       void *shmat(int shmid, const void *shmaddr, int shmflg);
       函数参数:
	   int shmid, 共享内存的id号
	   const void *shmaddr, 共享内存的映射首地址,可设置为NULL
	   int shmflg 区域的权限设置一般写为0,作为可读可写
	              SHM_RDONLY 只读属性
       int shmdt(const void *shmaddr);取消映射
	   函数参数:
	   const void *shmaddr 需要取消的首地址  shmat函数返回值

共享内存属性

NAME   共享内存的属性
       shmctl - System V shared memory control
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>
       函数原型:
       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       函数参数:
	   int shmid, 共享内存的ID号
	   int cmd, 共享内存的命令字
	     IPC_STAT 获取内存的属性,内存保存在 struct shmid_ds
         IPC_SET 设置内存的属性,通过属性结构体struct ipc_perm
         IPC_RMID 删除共享内存,第三参数设置为NULL  
	   struct shmid_ds *buf 属性结构体
	   struct shmid_ds {
               struct ipc_perm shm_perm; 共享内存的属性
               size_t          shm_segsz; 共享内存大小
               time_t          shm_atime; 共享内存映射时间
               time_t          shm_dtime; 共享内存释放时间
               time_t          shm_ctime; 共享内存修改时间
               pid_t           shm_cpid; 创建共享内存的进程号
               pid_t           shm_lpid; 最后映射共享内存进程号
               shmatt_t        shm_nattch; 共享内存的链接数
               ...
           };
      返回值: 成功  0  失败 -1

共享内存通信步骤

操作方式

  • 申请共享内存所需的键值号key ftok()
  • 根据key键申请共享内存的id号 shmget()
  • 通过共享内存ID号进行共享内存的映射 shmat()
  • 通过共享内存的首地址shmaddr 进行数据的读写操作 scanf()/printf()
  • 共享内存不使用时,接触共享内存的映射 shmdt()
  • 共享内存进行删除或者获取属性操作 shmctl
#include "myhead.h"

int main()
{
	//1. 申请共享内存所需的键值号key  ftok()
	key_t key=ftok(".",20);
	if(key == -1)
	{
		perror("ftok failed\n");
		return -1;
	}
	
	//2. 根据key键值申请共享内存的id号  shmget()
	int shmid=shmget(key,1024,IPC_CREAT|IPC_EXCL|0600);
	//共享内存key资源已存在,则直接获取共享内存id
	if(shmid == -1&&errno == EEXIST)
	{
		shmid=shmget(key,1024,0600);//获取共享内存id
	}
	else if(shmid == -1)//申请失败
	{
		perror("shmid failed\n");
		return -1;
	}
	
	//3. 通过共享内存ID号进行共享内存的映射 shmat()
	char *p=(char *)shmat(shmid,NULL,0);
	if(p == NULL)
	{
		printf("shmat failed\n");
		return -1;
	}
	
	//4. 通过共享内存的首地址shmaddr进行数据的读写操作 scanf()/printf()
	printf("请输入数据\n");
	scanf("%s",p);
	
	//5. 共享内存不使用时,解除共享内存的映射 shmdt()
	shmdt(p);
	
	//6. 共享内存进行删除或者获取属性操作 shmctl()
	struct shmid_ds ds;
	shmctl(shmid,IPC_STAT,&ds);//获取属性
	printf("共享内存大小%ld\n",ds.shm_segsz);
	printf("共享内存创建进程号%d\n",ds.shm_cpid);
	printf("共享内存连接数%ld\n",ds.shm_nattch);
	return 0;
}

#include "myhead.h"

int main()
{
	//1. 申请共享内存所需的键值号key  ftok()
	key_t key=ftok(".",20);
	if(key == -1)
	{
		perror("ftok failed\n");
		return -1;
	}
	
	//2. 根据key键值申请共享内存的id号  shmget()
	int shmid=shmget(key,1024,IPC_CREAT|IPC_EXCL|0600);
	//共享内存key资源已存在,则直接获取共享内存id
	if(shmid == -1&&errno == EEXIST)
	{
		shmid=shmget(key,1024,0600);//获取共享内存id
	}
	else if(shmid == -1)//申请失败
	{
		perror("shmid failed\n");
		return -1;
	}
	
	//3. 通过共享内存ID号进行共享内存的映射 shmat()
	char *p=(char *)shmat(shmid,NULL,0);
	if(p == NULL)
	{
		printf("shmat failed\n");
		return -1;
	}
	
	//4. 通过共享内存的首地址shmaddr进行数据的读写操作 scanf()/printf()
	printf("共享内存数据:%s\n",p);
	
	//5. 共享内存不使用时,解除共享内存的映射 shmdt()
	shmdt(p);
	
	//6. 共享内存进行删除或者获取属性操作 shmctl()
	struct shmid_ds ds;
	shmctl(shmid,IPC_STAT,&ds);//获取属性
	printf("共享内存大小%ld\n",ds.shm_segsz);
	printf("共享内存创建进程号%d\n",ds.shm_cpid);
	printf("共享内存连接数%ld\n",ds.shm_nattch);
	return 0;
}

在这里插入图片描述

IPC通信之信号量

信号量概念

不是用于传输数据,属于多进程之间协调数据的一种措施,用于协调不同进程之间的共享数据资源,用于处理多进程之间作为互斥机制存在,一般用于共享资源的互斥保护,保证资源的申请与释放完成一读一写操作,通过整数计数值进行调整


当多个进程同时访问同一份资源的时候,该资源称为共享资源,也称临界资源,访问这类资源的代码称为临界代码,这些代码的操作区域称为临界区,程序进入临界区的时候,必须要对资源进行申请 ,这个动作称为p操作,程序离开临界区的时候,必须对资源进行释放,这个动作称为v操作

信号量查询

  • 信号量查询命令方式: ipcs -s
  • 信号量删除命令方式: ipcrm -s 信号量id / ipcrm -S 键值key
    在这里插入图片描述

信号量是具有原子性的操作变量,是由电气特性提供的计数值选择
信号量是一个非负数值,信号量表示一个资源的数量
若某个信号量的值变为负数值,则表示为阻塞等待,等待资源增加
p操作 此时对信号量使用减法操作,当信号量的值<0的时候进程阻塞
v操作此时对信号量使用加法操作,信号量的值>=0的时候进程畅通

信号量API

信号量ID号

NAME   信号量获取ID号
       semget - get a System V semaphore set identifier
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       函数原型:
       int semget(key_t key, int nsems, int semflg);
       函数参数:
	   key_t key, 信号量的需求键值key ftok函数获取
	   int nsems, 信号量的元素个数
	   int semflg 信号量的标识权限
	       IPC_CREAT 创建权限,若IPC不存在则创建对象
		   IPC_EXCL  判断IPC对象若存在,则退出创建操作
		            此时IPC对象的错误码为errno==EEXIST
		   mode 提供信号量的操作权限,八进制权限(例如0666)
	   返回值 成功  返回信号量的id号      失败 -1  

信号量属性设置

NAME  信号量的属性设置
       semctl - System V semaphore control operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       函数原型:
       int semctl(int semid, int semnum, int cmd, ...);
       函数参数:
	   int semid, 信号量的id
	   int semnum, 信号量的对象,第几个信号量对象
	   int cmd, 信号量相关的命令字
	       IPC_STAT  获取信号量的属性 struct semid_ds;
		   IPC_SET   设置信号量的权限 struct ipc_perm;
		   IPC_RMID  删除信号量,命令参数设为NULL
		   GETALL    获取所有的信号量
		   GETVAL    获取信号量对应的元素值
		   SETALL    设置所有的信号量
		   SETVAL    设置信号量对应的元素值
	   ... 根据命令字,选择配置结构体或者共用体
	     union semun {
		   int              val;/*Value for SETVAL*/
		   struct semid_ds *buf;/*Buffer for IPC_STAT, IPC_SET*/
		   unsigned short  *array; /* Array for GETALL, SETALL */
		   struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
           };
例如:
semctl(semid,0,SETVAL,semun.val);//设置信号量元素初始值
semctl(semid,0,GETVAL);//获取信号量元素初值
semctl(semid,0,IPC_STAT,semun.buf);//获取信号量的属性

信号量pv操作

NAME  信号量的设置
       semop, semtimedop - System V semaphore operations
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       函数原型:
       int semop(int semid, struct sembuf *sops, size_t nsops);
       函数参数:
	   int semid, 信号量的ID号
	   struct sembuf *sops, 信号量动作结构体
	   struct sembuf{
		   unsigned short sem_num; 信号量元素 从0开始
           short          sem_op;  信号量增减 p操作-1  v操作+1
           short          sem_flg; 操作选项 SEM_UNDO还原信号量
	   };
	   size_t nsops 信号量的设置个数

信号量步骤
操作步骤

  • 获取信号量所需要的系统键值key ftok
  • 根据系统键值key获取信号量id semget
  • 设置信号量的元素初始值 semctl(SETVAL)
  • 信号量资源进行数据的pv操作 semop
  • 信号量资源的删除操作 semctl(IPC_RMID)
#include "myhead.h"

int main()
{
	//1. 获取信号量所需要的系统键值key  ftok()
	key_t key=ftok(".",10);
	if(key == -1)
	{
		perror("ktok failed\n");
		return -1;
	}
	
	//2. 根据系统键值key获取信号量ID  semget()
	int semid=semget(key,1,IPC_CREAT|IPC_EXCL|0666);
	//若信号量对应key存在,则直接打开信号量对象
	if(semid == -1 && errno == EEXIST)
	{
		semid=semget(key,1,0666);//直接打开信号量获取id
	}
	else if(semid == -1)
	{
		perror("semget failed\n");
		return -1;
	}
	
	//3. 设置信号量的元素初始值 semctl(SETVAL)
	printf("信号量元素0的值%d\n",semctl(semid,0,GETVAL));
	int ret=semctl(semid,0,SETVAL,5);//设置元素0的初值为5
	if(ret == -1)
	{
		perror("SETVAL failed\n");
		return -1;
	}
	printf("信号量初始值%d\n",semctl(semid,0,GETVAL));
	
	//4. 信号量资源进行数据的pv操作 semop()
	struct sembuf  sop;//结构体初始化
	sop.sem_num=0; //信号量元素 从0开始
    sop.sem_op=-1;  //信号量增减 p操作-1  v操作+1
    sop.sem_flg=SEM_UNDO; //操作选项 SEM_UNDO还原信号量
	for(int i=0;i<6;i++)//执行p操作
	{
		semop(semid,&sop,1);//p操作-1
		printf("信号量元素0的值%d\n",semctl(semid,0,GETVAL));
	}
	
	
//5. 信号量资源的删除操作 semctl(IPC_RMID)
	return 0;
}

在这里插入图片描述

通信方式优点缺点
共享内存通信效率高,直接读取共享内存不需要额外的拷贝,像管道跟消息队列都需要内核跟用户交互4次,共享内存只需要2次,共享内存的数据只有发生覆盖重写内容才回发生变化没有同步机制,多进程间通信的时候需要借助信号量,进行多进程之间的临界资源协调,当共享内存进行实时读取和写入的时候,通过信号量的阻塞和释放协调多进程之间的访问,防止强度数据
消息队列适用于选择性通信,根据标识不同选择不同的消息接收,适用于任务分配,消息队列属于异步通信机制,只要消息发出,无须例会是否存在进程进行消息接收,只要有进程读取数据,则该队列该消息会被删除清空可用性比较低,读取后就会删除处理,保证不了消息的重复性,不适用于大大量数据交互,容易丢失数据

信号量 signal

所有信号的操作都有共性

  • 简单
  • 不能携带大量的信息
  • 满足某个特定条件才能发送在linux中信号是一种特殊的管道通信,但是在linux进程通信中属于唯一的异步(只管发不管收)通信方式,除了几个特殊的信号以外,其他信号都是无法预料的,当进程接收到信号之后,信号处理方式与cpu收到中断请求处理方式一致,进程负责捕获信号、发送信号以及信号处理函数

信号的处理过程

递达:递送信号到进程对象中,能够捕获信号并执行处理函数
未决:产生和递达之间的状态,主要因为屏蔽或者阻塞导致接收信号暂停
linux内核的进程控制块PCB是一个机构体,在该结构体中包含信号相关的处理方式,主要是阻塞信号集合未决信号集

阻塞信号集(信号屏蔽集),将某些信号加入到集合当中,然后对其进行屏蔽操作,在屏蔽之后进程接受信号的时候,该信号会被推后处理(解除屏蔽);
未决信号集,信号产生之后,未决信号集中描述该信号的位置被置为1,表示该信号处于未决状态,当信号处理完毕之后,该信号位立即反转为0;信号产生之后由于某些原因导致无法递达,这类信号属于未决信号集。
发生信号的来源主要分为硬件来源和软件来源,通过linux内核发送不同的信号,然后判断该信号是否发生阻塞,然后判断信号的响应模式,最后完成响应的处理动作。

信号的种类

可靠实时信号不可靠非实时信号
区别:
早期的系统信号都是比较简单和原始,信号值从1~31进行描述,这部分信号都是非实时不可靠信号,主要问题就是不支持排队等待和信号存在丢失。

非实时信号(不可靠信号):

  1. 非实时信号不支持排队,信号的响应会相互嵌套
  2. 若目标进程中没有及时响应信号,那么随后到达的信号会导致该信号丢失
  3. 每个非实时信号都对应一个系统事件,事件发生就会触发该信号
  4. 进程中存在实时和非实时信号的时候,那么进程优先执行实时信号

实时信号(可靠信号):

  1. 实时信号的响应次序都是按照排队顺序出现,不会相互嵌套
  2. 目标进程中实时信号会被同时多次发生,且不会丢失并挨个响应
  3. 实时信号都是没有对应系统事件
  4. 实时信号的响应都是从大到小依次响应

在这里插入图片描述
信号的生命周期:信号产生、信号注册、信号响应和处理、信号销毁

查看信号值
命令:kill -l 或者trap -l
发送信号的格式:
进程号响应模式: kill -信号值 进程号
进程名响应模式:killall -信号值 进程名
在这里插入图片描述
在这里插入图片描述

信号的响应动作

信号的响应三种方式:忽略动作、默认动作和捕获动作

  1. 忽略动作:忽略信号里面大部分都可以执行,但是暂停信号SIGSTOP和杀戮信号SIGKILL不能忽略,不可捕获,不会阻塞。
  2. 默认动作:执行默认系统动作(linux系统下都有规定1-31信号的默认动作),根据系统的规定动作执行任务。
  3. 捕获动作:当捕获到信号之后,需要自定义完成信号处理函数执行任务

信号执行顺序:

  1. 若信号被阻塞,那么该信号将会挂起不做任何处理,直到解除阻塞之后,信号才会响应进程任务
  2. 若信号被捕获,那么进一步判断信号的响应方式
    若为忽略动作,那么直接丢弃该信号,不做任何响应
    若为默认动作,那么执行系统的默认操作事件
    若为捕获动作,那么根据自定义动作执行响应操作

信号捕获与发送

NAME  信号响应处理函数
    signal - ANSI C signal handling
SYNOPSIS
    #include <signal.h>
    函数指针 将函数指针取别名 sighandler_t==>void (*)(int)
    typedef void (*sighandler_t)(int);
    函数原型:
sighandler_t signal(int signum, sighandler_t handler);
    函数参数:
    int signum, 捕获的信号值
    sighandler_t handler 信号的响应处理动作
     SIG_IGN  忽略信号动作
     SIG_DFL  默认信号动作
     void 函数名(int) 自定义信号动作,函数中int表示为信号值
    返回值:
      成功 返回函数指针所指向函数对象地址
      失败 返回SIG_ERR
NAME 发送一个信号给进程
       kill - send signal to a process
SYNOPSIS
       #include <sys/types.h>
       #include <signal.h>
       函数原型:
       int kill(pid_t pid, int sig);
       函数参数:
       pid_t pid  进程号 
       int sig    信号值
       返回值: 成功  0  失败 -1
NAME  等待一个信号值
       pause - wait for signal
SYNOPSIS
       #include <unistd.h>
       函数原型:
       int pause(void);
       返回值:
           成功  收到非致命信号或者信号已经被捕获
           失败   返回 -1
                收到致命信号导致进程退出 不返回
#include "myhead.h"

//自定义信号响应函数
void func(int sig)
{
	printf("当前捕获的信号值%d\n",sig);
}

int main()
{
	//获取进程的进程号
	printf("当前进程号为%d\n",getpid());
	//捕获信号值 2号信号
	//signal(2,SIG_IGN);//忽略信号动作
	//signal(2,SIG_DFL);//默认信号动作
	signal(2,func);//捕获信号动作
	//保证信号捕获 挂起
	pause();	
	return 0;
}

在这里插入图片描述
信号的响应顺序:从实时信号响应到非实时信号,从大到小响应

#include "myhead.h"

//自定义信号响应函数
void func(int sig)
{
	printf("当前捕获的信号值%d\n",sig);
}

int main()
{
	//获取进程的进程号
	printf("当前进程号为%d\n",getpid());
	//循环捕获所有信号 除了SIGKILL和SIGSTOP以外
	for(int i=1;i<=64;i++)
	{
		//跳过特殊信号
		if(i==9 || i==19 || i==32 || i==33)
			continue;
		//能够捕获读取信号值
		signal(i,func);
	}

	while(1)
	{
		//保证信号捕获 挂起
		pause();
	}
	return 0;
}
#include "myhead.h"

int main(int argc,char *argv[])
{
	//将所有的信号发送给某个进程,除了SIGKILL和SIGSTOP以外
	for(int i=1;i<=64;i++)
	{
		//跳过特殊信号
		if(i==9 || i==19 || i==32 || i==33)
			continue;
		//发送信号给某个进程
		kill(atoi(argv[1]),i);
	}
	return 0;
}

在这里插入图片描述

特殊信号的发送方式

时钟信号的发送,定时一段时间,然后发送该信号

NAME  设置一个时钟信号进行发送该信号
   alarm - set an alarm clock for delivery of a signal
SYNOPSIS
   #include <unistd.h>
   函数原型:
   unsigned int alarm(unsigned int seconds);
   函数参数:
   unsigned int seconds 秒数 定时的时长
#include "myhead.h"

//自定义响应动作
void func(int sig)
{
   printf("到点了,该上厕所了,信号值%d\n",sig);	
}

int main()
{
	//捕获定时器信号
	signal(14,func);
    //间隔5秒发送时钟信号
	alarm(5);
    //挂起进程
	pause();
	return 0;
}

在这里插入图片描述
给自身发送信号,自身的signal函数进行捕获响应

NAME  发送一个信号给自己
       raise - send a signal to the caller
SYNOPSIS
       #include <signal.h>
       函数原型
       int raise(int sig);
       函数参数: int sig 信号值
       返回值: 成功 0  失败 非0
       raise(9)  相当于 kill(getpid(),9);
#include "myhead.h"
//信号响应函数
void func(int sig)
{
    printf("自身接受的信号为%d\n",sig);	
}

int main()
{
	//捕获 10号信号
	signal(10,func);
	//延时5秒发送信号
	sleep(5);
	raise(10);//发送10号信号
	//延时5秒之后9号
	printf("5秒钟后自杀\n");
	sleep(5);
	raise(9);//杀戮
	//挂起进程
	pause();
	return 0;
}

在这里插入图片描述

携带数据传递信号

信号的发送

NAME  发送一个信号和数据给进程
   sigqueue - queue a signal and data to a process
SYNOPSIS
   #include <signal.h>
   函数原型
   int sigqueue(pid_t pid, int sig, const union sigval value);
   函数参数:
   pid_t pid, 发送对象的进程号 
   int sig,   需要发送信号值
   const union sigval value 额外携带数据参数共用体
   union sigval {联合体可以发送整型和指针
           int   sival_int;//携带一个整型数据
           void *sival_ptr;//携带地址类型数据
       };
   返回值: 成功  0  失败 -1

信号的接收

NAME  捕获信号对象
   sigaction - examine and change a signal action
SYNOPSIS
   #include <signal.h>
   函数原型:
   int sigaction(int signum, const struct sigaction *act,
                 struct sigaction *oldact);
   函数参数:
   int signum,   需要捕获的信号值数据  信号值
   const struct sigaction *act,信号动作结构体设置
   struct sigaction *oldact 原来的信号动作结构体
                            若不存在原来的信号动作设为NULL
    struct sigaction {信号动作结构体
       void  (*sa_handler)(int);
       和signal函数作用一致,作为普通的信号响应函数处理 
       void  (*sa_sigaction)(int, siginfo_t *, void *);
       当需要捕获信号以及携带参数时,通过siginfo_t类型进行获取
       该类对象用于保存对方传递的数据值 void *补充数据
        siginfo_t {
             int si_int;  /*POSIX保存信号携带整数 */
             void *si_ptr; /* POSIX保存信号携带地址 */
       };
       sigset_t   sa_mask; 设置阻塞的信号集对象
       int        sa_flags;选择标识,决定信号接收方式
        标识设置为0,则表示选择sa_handler普通响应模式 信号捕获
        标识设置为SA_SIGINFO,则表示选择sa_sigaction参数携带
       void     (*sa_restorer)(void);废弃的数据域
   };
#include "myhead.h"

//自定义普通函数响应
void func(int sig)
{
	printf("当前为普通函数响应,信号值%d\n",sig);
}

//携带参数的信号响应
void funs(int sig, siginfo_t *info, void *arg)
{
	printf("当前捕获的信号值为%d\n",sig);
	printf("当前除了捕获信号以外还获取到整数%d\n",info->si_int);
}

int main()
{
	//获取进程的进程号
	printf("当前进程号为%d\n",getpid());
	
	//捕获10号信号 并且按照普通函数响应
	struct sigaction act;//实例化信号动作结构体
	//配置自定义结构体成员,选择普通响应函数和选择标识
	act.sa_handler=func;//普通自定义信号处理函数
	act.sa_flags=0;//标识设置为0 普通响应
	//捕获10号信号值
	sigaction(10,&act,NULL);
	
	//捕获12号信号 并且按照携带参数信号响应
	struct sigaction action;//实例化信号动作结构体
	//配置自定义结构体成员,选择额外携带响应函数和选择标识
	action.sa_sigaction=funs;//选择携带参数信号响应
	action.sa_flags=SA_SIGINFO;//标识设置SA_SIGINFO携带获取
	//捕获12号信号值
	sigaction(12,&action,NULL);
	
	//持续运行
	while(1);
	return 0;
}
#include "myhead.h"

int main(int argc,char *argv[])
{
	//准备携带参数的数据值
	union sigval value;
	//设置共用的整数值100
	value.sival_int=100;
	//发送信号数据
	sigqueue(atoi(argv[1]),atoi(argv[2]),value);
	return 0;
}

在这里插入图片描述

信号集(阻塞/未决)

在信号中,阻塞就是将信号直接挂起,进程不受指定信号的干扰,信号不影响进程的执行方式,直到解除阻塞,在通过信号影响进程的运行,相当于接受到信号之后,由于阻塞关系暂时不执行,解除阻塞之后在执行

信号阻塞掩码集

信号集的阻塞掩码指的是linux定义"sigset_t"类型的变量,该类型的数据在linux当中存放所有的阻塞信号的集合,将多个信号集中到一个处理当中形成信号集,sigset_t类型变量其实为一个结构体描述,在这个结构体当中存放一个整型变量,这个整型变量存储linux当中的信号值

NAME  信号集处理函数
   sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX signal set operations
SYNOPSIS
   #include <signal.h>
   函数原型:信号集管理
   //清空信号集对象set
   int sigemptyset(sigset_t *set);
   //将所有的信号加入到集合set
   int sigfillset(sigset_t *set);
   //将指定信号signum加入到集合set
   int sigaddset(sigset_t *set, int signum);
   //将指定信号signum从集合set中删除
   int sigdelset(sigset_t *set, int signum);
   函数参数:
   sigset_t *set, 管理信号集合
   int signum     指定信号管理
   返回值:成功  返回0       失败 -1
   //判断某个信号signum是否存在于集合set当中
   int sigismember(const sigset_t *set, int signum);
   函数参数:
   const sigset_t *set, 管理信号集合
   int signum     指定信号管理
   返回值:
     返回为1 表示信号存在于集合当中
     返回为0 表示信号不存在于集合当中
     返回为-1 表示判断失败
#include "myhead.h"

int main()
{
	//定义信号集
	sigset_t set;
	//清空信号集
	sigemptyset(&set);
	//将所有的信号加入到集合
	sigfillset(&set);
	//判断指定信号是否存在于集合当中
	for(int signum=1;signum<=64;signum++)
	{
		//判断信号是否存在
		int ret=sigismember(&set,signum);
		if(ret == 1)
			printf("signum:%d存在于集合set\n",signum);
		else if(ret == 0)
			printf("signum:%d不存在于集合set\n",signum);
		else
			printf("判断失败\n");
	}
	//删除集合中的某些信号
	for(int signum=1;signum<=64;signum++)
	{
		if(signum%3 == 0)
			sigdelset(&set,signum);
	}
	//判断指定信号是否存在于集合当中
	for(int signum=1;signum<=64;signum++)
	{
		//判断信号是否存在
		int ret=sigismember(&set,signum);
		if(ret == 1)
			printf("signum:%d存在于集合set\n",signum);
		else if(ret == 0)
			printf("signum:%d不存在于集合set\n",signum);
		else
			printf("判断失败\n");
	}
	return 0;
}

在这里插入图片描述
阻塞信号集

设置信号集的阻塞和接触,完成一段时间内不受信号的控制影响,阻塞不同于忽略,阻塞是信号挂起,暂时不响应,解除阻塞之后,信号再次响应操作,忽略则是直接将信号丢弃

NAME  信号集的阻塞操作
   sigprocmask - examine and change blocked signals
SYNOPSIS
   #include <signal.h>
   函数原型:
   int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
   函数参数:
   int how,		设置信号集的方式 
        SIG_BLOCK  设置信号集的阻塞,在原有信号集之上
        SIG_UNBLOCK 设置信号集解除阻塞
        SIG_SETMASK 设置信号集的阻塞 使用old替换set
   const sigset_t *set,需要设置阻塞的信号集
   sigset_t *oldset 原来的信号集,若无需关注可写为NULL
   返回值: 成功  0       失败 -1
#include "myhead.h"

void func(int sig)
{
	printf("捕获的信号值为%d\n",sig);
}

int main()
{
	printf("当前的进程号为%d\n",getpid());
	//定义信号集
	sigset_t set;
	//操作信号集
	sigemptyset(&set);//清空信号
	sigfillset(&set);//添加所有信号
	//捕获3号信号
	signal(3,func);//捕获
	signal(64,func);//捕获
	//添加信号集阻塞
	printf("添加阻塞\n");
	sigprocmask(SIG_BLOCK,&set,NULL);
	//延时20秒
	sleep(20);
	//添加信号集解除
	printf("解除阻塞\n");
	sigprocmask(SIG_UNBLOCK,&set,NULL);
	//等待信号捕获
	pause();
	return 0;
}

在这里插入图片描述

信号集未决

NAME  读取当前进程的未决信号集
   sigpending - examine pending signals
SYNOPSIS
   #include <signal.h>
   函数原型:
   int sigpending(sigset_t *set);
   函数参数:sigset_t *set 获取需要读取的对象
   返回值:成功  返回0    失败 返回-1
#include "myhead.h"

//检测未决信号
void print(sigset_t *set)
{
	//循环检测每个信号
	for(int i=1;i<=64;i++)
	{
		//判断信号是否存在集合中
		int ret=sigismember(set,i);
		if(ret == 1)
		{
			putchar('1');
		}
		else
		{
			putchar('0');
		}
	}
	printf("\n");
}

int main()
{
	//定义信号集
	sigset_t set,pedset;
	//操作信号集
	sigemptyset(&set);//清空信号
	//添加指定信号
	sigaddset(&set,2);
	sigaddset(&set,3);
	//添加信号集阻塞
	printf("添加阻塞\n");
	sigprocmask(SIG_BLOCK,&set,NULL);
	//检测未决
	while(1)
	{
		//未决检测
		int ret=sigpending(&pedset);
		if(ret == -1)
		{
			printf("检测失败\n");
			break;
		}
		//获取未决数据
		print(&pedset);
		sleep(1);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请叫我斌哥哥

给打赏的我会单独一对一讲解

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值