Linux进程间通信(管道、消息队列、共享内存、信号、信号量)

Linux进程间通信概述

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC 的方式通常有管道(包括无名管道和命名管道)、消息队列、共享内存、信号、信号量、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。
在这里插入图片描述

1.管道

无名管道(pipe)

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

特点:

1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端(fd[0])和写端(fd[1])。

2、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

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

4、管道中的数据读走就没了

原型:

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

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。要关闭管道只需将这两个文件描述符关闭即可。如下图:
在这里插入图片描述
在这里插入图片描述
例子:
创建管道后,在父进程中写入,在子进程中读

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2];
	pid_t pid;
	char readBuf[128];
	int nwrite;
	int nread;
	// int pipe(int pipefd[2]);
	if(pipe(fd) == -1){
		printf("创建管道失败\n");
	}
	pid=fork();//创建子进程
	if(pid == -1){
		printf("创建子进程失败\n");
	//1.在父进程写入
	}else if(pid > 0){
		printf("这是父进程\n");
		close(fd[0]);//关闭读文件
		//ssize_t write(int fd, const void *buf, size_t count);
		nwrite=write(fd[1],"hello from father process",strlen("hello from father process"));
		wait(NULL);
		
	//2.在子进程读
	}else{
		printf("这是子进程\n");
		close(fd[1]);
		//ssize_t read(int fd, void *buf, size_t count);
		nread=read(fd[0],readBuf,128);//如果度端没有内容,会阻塞等待
		printf("来自父进程的写入内容是:%s\n",readBuf);
		exit(0);
	}

	return 0;
}

注意: 管道为半双工通信,读和写操作在同一时间内只能进行一个,所以在读的时候要关闭写端,写的时候关闭读端。
运行结果:

在这里插入图片描述

有名管道(fifo)

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

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

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)的区别:

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

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

创建管道例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>


int main()
{
	//int mkfifo(const char *pathname, mode_t mode);
	if(mkfifo("./file",0600) == -1 && errno != EEXIST){
		printf("mkfifo创建失败\n");
		perror("why:");
	}
	else{
		if(errno == EEXIST){
			printf("文件已经存在\n");
		}
		printf("mkfifo创建成功\n");
	}

	return 0;
}

使用有名管道通信的例子

read代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
        int nread;
        mkfifo("./file",0600);
        char buf[1024] = {0};
        int fd = open("./file",O_RDONLY);//若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO
        printf("open success\n");
        while(1){
                nread = read(fd,buf,1024);
                printf("read %d byte ,neirong:%s\n",nread,buf);
        }
        return 0;
}

write代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
        mkfifo("./file",0600);
        char *str = "this is a fifo demo!";
        int fd = open("./file",O_WRONLY);
        printf("open success\n");
        while(1){
                write(fd,str,strlen(str));
                sleep(1);
        }
        return 0;
}

运行read会阻塞,一直到运行write后read才会继续往下执行。

个人感觉很像文件操作,不过不用进行lseek等操作,内核知道这是管道操作,数据读出时管道数据清除。

2.消息队列(msg)

消息队列的通信原理

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

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

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除(Linux内核机制进行管理)。

消息队列可以实现消息的随机查询(链表机制就是这样的),消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

消息队列的通信原理理解图:
在这里插入图片描述

消息队列相关api

1 #include <sys/msg.h>

2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);

4 // 发送消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);

6 // 接收消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);

8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

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

1、如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
2、key参数为IPC_PRIVATE。

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

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

消息队列收发数据

readmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf readBuf;
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);



	return 0;
}

sendmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf sendBuf={888,"this message from que"};;
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞



	return 0;
}

运行结果:
先运行readmsg阻塞在那等待发送消息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实现双方消息通信都能发送和接收

readmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf readBuf;
	struct msgbuf sendBuf={998,"thank you que, i have received"};
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");



	return 0;
}

sendmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf sendBuf={888,"this message from que"};
	struct msgbuf readBuf;
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(0x1234,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);



	return 0;
}

在这里插入图片描述

键值生成

readmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf readBuf;
	struct msgbuf sendBuf={998,"thank you que, i have received"};
	//key_t ftok(const char *pathname, int proj_id);
	key_t key;
	key=ftok(".",'z');//"."当前路径, proj_id典型的用法是将一个ASCII码作为proj_id,随便取值都可以  内核这两者组合出一个键值
	printf("key = %x\n",key);//用16进制输出
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");



	return 0;
}

sendmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf sendBuf={888,"this message from que"};
	struct msgbuf readBuf;
	key_t key;
	key=ftok(".",'z');//"."当前路径,proj_id典型的用法是将一个ASCII码作为proj_id
	printf("key = %x\n",key);//用16进制输出
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);



	return 0;
}

在这里插入图片描述

消息队列移除

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

struct msqid_ds *buf这里我们一般写NULL
CMD类型参照下图(箭头指的就是i用的最多的,把消息队列生成的链表在内核里移除):
在这里插入图片描述
例如:
readmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf readBuf;
	struct msgbuf sendBuf={998,"thank you que, i have received"};
	//key_t ftok(const char *pathname, int proj_id);
	key_t key;
	key=ftok(".",'z');//"."当前路径,proj_id典型的用法是将一个ASCII码作为proj_id
	printf("key = %x\n",key);//用16进制输出
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");
	msgctl(msgId,IPC_RMID,NULL);



	return 0;
}

sendmsg

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
	struct msgbuf sendBuf={888,"this message from que"};
	struct msgbuf readBuf;
	key_t key;
	key=ftok(".",'z');//"."当前路径,proj_id典型的用法是将一个ASCII码作为proj_id
	printf("key = %x\n",key);//用16进制输出
	//int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);//如果存在就打开,没有则创建权限是可读刻写可执行
	if(msgId == -1){
		printf("创建失败\n");
	}
	//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);
	msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//0是非阻塞
	printf("发送完毕\n");
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),998,0);//0是非阻塞方式
	printf("读取来自队列的内容:%s\n",readBuf.mtext);
	//int msgctl(int msqid, int cmd, struct msqid_ds *buf);

	msgctl(msgId,IPC_RMID,NULL);


	return 0;
}

在这里插入图片描述

3.共享内存(shm)

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

特点

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

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

信号量+共享内存通常结合在一起使用(最后一节讲解),信号量用来同步对共享内存的访问。

和消息队列的区别:

消息队列就像两个人聊天,一方要将要说的话写在一张纸上放入箱子里,另外一个人去箱子里取出来阅读。这个箱子(消息队列)不会自行销毁,要调用msgctl才可以。

共享内存就好像两个学生在上课的时候,由于不能说话,就只好拿一个本子来聊天,这个本子就相当于共享的内存,双方可以同时看到纸上的内容。比消息队列效率高。调用shmctl删除共享内存。

在这里插入图片描述

相关api原型

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);           

参数说明:

key为ftok生成的键值
size为共享内存的长度,以字节为单位
当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
flag为所需要的操作和权限,可以用来创建一个共享存储空间并返回一个标识符或者获得一个共享标识符。
----flag的值为IPC_CREAT:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。
----flag的值为 IPC_CREAT |IPC_EXCL:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则产生错误。
cmd 常用的是IPC_RMID从系统中删除该共享内存

例子
创建共享内存并写入数据

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



int main()
{


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

	//int shmget(key_t key, size_t size, int shmflg);
	//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
	int shmId=shmget(key,1024*4,IPC_CREAT|0666);//共享内存大小必须以字节为单位
	if(shmId == -1){
		printf("创建共享内存失败\n");
		exit(-1);//异常退出
	}

	//void *shmat(int shmid, const void *shmaddr, int shmflg);
	//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
	shmaddr=shmat(shmId,0,0);//挂载映射,如果引用一个已存在的共享内存(上方改革创建),则将 size 指定为0 
	printf("shmat ok\n");
	strcpy(shmaddr,"hello sharemessage");
	
	sleep(5);

	//int shmdt(const void *shmaddr);
	// 断开与共享内存的连接
	shmdt(shmaddr);

	 //int shmctl(int shmid, int cmd, struct shmid_ds *buf);
	//控制共享内存的相关信息:成功返回0,失败返回-1
	shmctl(shmId,IPC_RMID,0);//IPC_RMID删除消息队列
	printf("退出\n");
	return 0;
}

读共享内存的内容

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



int main()
{


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

	//int shmget(key_t key, size_t size, int shmflg);
	//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
	int shmId=shmget(key,1024*4,0);//只要打开就行不必创建
	if(shmId == -1){
		printf("创建共享内存失败\n");
		exit(-1);//异常退出
	}

	//void *shmat(int shmid, const void *shmaddr, int shmflg);
	//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
	shmaddr=shmat(shmId,0,0);//挂载映射,如果引用一个已存在的共享内存,则将 size 指定为0 
	printf("shmat ok\n");
	
	printf("内容是:%s\n",shmaddr);
	//int shmdt(const void *shmaddr);
	 //断开与共享内存的连接
	shmdt(shmaddr);
	return 0;
}

在这里插入图片描述

4.信号(sig)

本节参照博文:https://www.jianshu.com/p/f445bfeea40a

信号概述

1.信号:对Linux来说就是软中断,与单片机的硬件中断(串口)类似。如在linux中输入 ctrl+c 来停止一个程序

2.信号的名字与编号:可在linux中通过 kill -l 查询(Linux系统一共有64个信号,编号1-64。不存在0信号,0信号有特殊的应用:在系统级的应用中被占用)

在这里插入图片描述

部分信号的说明:

2)SIGINT:ctrl+c 终止信号

3)SIGQUIT:ctrl+\ 终止信号

20)SIGTSTP:ctrl+z 暂停信号

26)SIGALRM:闹钟信号 收到此信号后定时结束,结束进程

17)SIGCHLD:子进程状态改变,父进程收到信号

9)SIGKILL:杀死信号

3.信号处理的三种方式:忽略,捕捉和默认动作

1、忽略:就跟字面意思一样忽略掉它(注意:SIGKILL,SIGSTOP不能被忽略

2、捕捉:就是一些信号处理的函数,然后让这个函数告诉内核,当信号产生时,内核调用该函数,实现某种信号的处理

3、默认动作:每个信号都有其对应的默认的处理动作,当触发了某种信号,系统就会立刻去执行。

4.信号的使用

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

对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯(即信号处理方式第二种–捕捉)的手段,那么如何来自定义信号的处理函数呢?

信号编程(入门)

信号处理函数的注册(信号绑定):signal (入门),sigaction(高级,可携带信号)
信号处理发送函数: kill (入门), sigqueue(高级,可携带信号)

信号绑定

#include <stdio.h>
#include <signal.h>
 
//      typedef定义了一种类型sighandler_t
       //typedef void (*sighandler_t)(int);//函数指针(指向函数的指针),无返回值,传入参数为一个int型
       //sighandler_t signal(int signum, sighandler_t handler);
//     返回这种类型                   带有_t表示结构体  函数指针变量
void handler(int signum){
 
        switch(signum){
                case 2:
                        printf("get SIGINT,signum=%d\n",signum);
                        break;
 
                case 9:
                        printf("get SIGKILL,signum=%d\n",signum);
                        break;
                case 10:
                        printf("get SIGUSR1,signum=%d\n",signum);
                        break;
        }
        printf("never quit\n");
}
 
int main(){
 
                      //函数指针 指向函数
        signal(SIGINT,handler);//捕捉信号 SIGINT是ctrl c的指令
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
        for(;;);//等于while(1);
 
        return 0;
}

编译运行,在键盘上输入 ctrl+c,结果为:
在这里插入图片描述
信号发送(杀死程序的信号 SIGKILL)

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



//typedef void (*sighandler_t)(int);

//sighandler_t signal(int signum, sighandler_t handler);



int main(int argc,char **argv)
{
	int signum;
	int pid;
	char cmd[128]={0};
	signum=atoi(argv[1]);//将字符串str转换成一个整数并返回结果
	pid=atoi(argv[2]);
	printf("signum=%d pid=%d\n",signum,pid);
	// int kill(pid_t pid, int sig);
	
	//方法一:
	//kill(pid,signum);   发送信号x
	
    //方法二 system调用脚本
	sprintf(cmd,"kill -%d %d",signum,pid);
	system(cmd);
	
	printf("发送指令成功\n");
	
	return 0;
}	

运行结果:
在这里插入图片描述
信号的忽略:

signal(SIGINT,SIG_IGN)//可以忽略掉ctrl c 的信号

信号携带消息(高级)

信号携带消息思路:

发信号

1.用什么发 sigqueue()
2.怎么发消息

#include <signal.h>
            //发给谁   发什么信号(比如上面讲的信号编号9 10等)  
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {                   //发送的消息(接收端也是用的这个联合体)  
   int   sival_int;//发送整型
   void *sival_ptr;//发送字符串
 };

收信号

1.用什么绑定函数(以及收到信号如何处理动作)sigaction()
2.如何读出消息

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact,是否备份,不备份用NULL

struct sigaction {   凡是带有_t说明是个结构体

   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,和signal()的参数handler一样了 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只能任选其一

 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 */
}

信号携带消息编程实现

收信号nicesignal.c

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
/*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     handler(int signum, siginfo_t *info, void *context)//info里面的参数可以通过man手册查询
{
	printf("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("发送者的pid=%d\n",info->si_pid);
	}
}

int main()
{
	
	struct sigaction act;
	printf("pid=%d\n",getpid());
	act.sa_sigaction=handler;
	act.sa_flags=SA_SIGINFO;//能够获取到信息信息
	//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
	sigaction(SIGUSR1,&act,NULL);//第三个是参数是备份的这边写NULL SIGUSR1对应的编号为10!!!
	printf("22");
	while(1);
	return 0;
}

发信号niceSendSig.c

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
	int signum;
	int pid;
	signum=atoi(argv[1]);//atoi把字符串转化为整形
	pid=atoi(argv[2]);//接收进程的pid号
	union sigval value;
	value.sival_int=100;//发送一个整型数100的消息
	/*union sigval {
               int   sival_int;
               void *sival_ptr;  发送字符的话把地址传进来
           };*/

	//int sigqueue(pid_t pid, int sig, const union sigval value);
	sigqueue(pid,signum,value);
	printf("我的pid是:%d\n",getpid());
	printf("发送完毕\n");
	return 0;
}

在这里插入图片描述

在这里插入图片描述

5.信号量(sem)

在这里插入图片描述

1.信号量:

信号量(Semaphore是一个计数器,用于实现进程间的互斥与同步,不用于存储进程间的通信数据

2.特点:

(1).用于进程间同步,若要在进程间传递数据需要结合共享内存

(2).信号量是基于PV操作,程序对信号量是原子操作

所谓原子操作,就是“不可中断的一个或一系列操作”,也就是不会被线程调度机制打断的操作

(3).对信号量的操作不仅限于对信号的+1,-1,可以是任意数(视频没讲)

P、V操作(类似信号量lock、unlock)

可以这样理解:一间房间(临界资源)的门前有一个盒子,盒子里有钥匙(信号量),一个人拿了钥匙(P操作),开了门并走进了房间,且门外还有人等着,得等进去的人出来放钥匙(V操作),这个人才能拿钥匙(P操作)进入房间

多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

4.相关api
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);

例子
目前是没有锁的状态,父进程想拿锁,在这边等待,然后子进程有锁直接放回,父进程得到锁后再运行。相当于不使用前面学习的wait方式保证子进程先运行,因为没有锁父进程卡在拿锁那边。

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


//semctl函数第三个参数cmd的宏要求后面定义一个联合体
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
                                           (Linux-specific) */
};


void pGetKey(int semid)//p操作“拿锁”
{
	struct sembuf set;
	/*sops[0].sem_num = 0;         //Operate on semaphore 0 
    sops[0].sem_op = 0;       		// Wait for value to equal 0 
    sops[0].sem_flg = 0;*/

	set.sem_num=0;//信号量编号 操作第几个信号量(锁) 这里就一个
	set.sem_op=-1;//拿锁
	set.sem_flg=SEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.
	                     //SEM_UNDO设置为当进程截止的时候,取消对锁的操作
	//int semop(int semid, struct sembuf *sops, size_t nsops); 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
	semop(semid,&set,1);//1代表第二个参数的个数
	printf("拿锁\n");
}


void vPutBackKey(int semid)//v操作“放回锁”
{
	struct sembuf set;
	/*sops[0].sem_num = 0;         //Operate on semaphore 0 
    sops[0].sem_op = 0;       		// Wait for value to equal 0 
    sops[0].sem_flg = 0;*/

	set.sem_num=0;
	set.sem_op=1;//放回锁
	set.sem_flg=SEM_UNDO;//If an operation specifies SEM_UNDO, it will be automatically undone when the process terminates.
	
	//int semop(int semid, struct sembuf semoparray[], size_t numops); 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
	semop(semid,&set,1);
	printf("放回锁\n");
}


int main(int argc, char const *argv[])
{
	key_t key;
	int semid;
	key=ftok(".",2);
	union semun initsem;
	initsem.val=0;//初始是没有锁状态
	
    //int semget(key_t key, int nsems, int semflg);创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
	semid=semget(key,1,IPC_CREAT|0666);//1:信号量组中有几个信号量 这边选择1个,IPC_CREAT|0666:如果有就获取没有就创建信号量组,权限可读可写可执行

    //int semctl(int semid, int sem_num, int cmd, ...); 控制信号量的相关信息
	semctl(semid,0,SETVAL,initsem);
	            //初始化信号量,int sem_num是代表操作第几个信号量,我这边写0代表操作第一个信号量(和数组一样 第一个从0开始),多个信号量要构造数组
			    //cmd可以通过man手册差相关的宏,SETVAL设置信号量的初值,设置为initsem(联合体),里面有1把琐
	
	int pid = fork();//创建子进程
	if(pid > 0){
		//去拿锁
		pGetKey(semid);//拿锁  P操作
		printf("这是父进程\n");
		vPutBackKey(semid);
		//放回锁	
		semctl(semid,0,IPC_RMID);//销毁锁	
	}else if(pid == 0){

		printf("这是子进程\n");
		vPutBackKey(semid);//放锁 V操作
	}else{
		printf("创建失败\n");
	}



	return 0;
}

锁初始化为0,子进程先运行,运行结束放锁(1),父进程拿锁(-1),然后放锁(1),最后销毁锁。
在这里插入图片描述

6.通信方式总结

1.管道(无名管道):速度慢,容量有限,只有父子进程能通讯

2.FIFO(有名管道):任何进程间都能通讯,但速度慢

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

4.信号量:不能传递数据,只能用来同步(P操作、V操作)

5.共享内存区:能够很容易控制容量,速度快,但要保持同步(和信号量结合使用),比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
在这里插入图片描述

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值