文章目录
概述
进程间通信:IPC(InterProcess Communication)
微信的聊天也是属于进程间通信,是基于网络的。
本篇的进程间通信基于单机的。
A进程和B进程之间创建一个通道,A和B都能从中读写数据,这是真正意义上的进程间通信。
进程间通信:
● 单机版:A和B跑在同一个PC上
● 多机版:A和B在不同的PC上(基于网络通信)
进程间通信(IPC)的方式有:
● 管道(无名管道和命名管道)
● 消息队列
● 信号量
● 共享内存
● Socket
● Streams
其中Socket和Streams支持不同主机上的两个进程IPC。
一、无名管道
管道
管道,通常指无名管道,是UNIX系统IPC最古老的形式。
特点:
- 它是半双工的,具有固定的读端和写端。
- 它只能用于具有亲缘关系的进程间通信(父子进程或兄弟进程之间)。
- 它可以看成一种特殊的文件,对于它的读写可以使用普通的read、write函数。但它不是普通的文件,它不属于任何文件系统,并且只存在于内存中。
同一时间,单向流动。读走就没。
【看成文件,但不是文件,而是在内存中】【有名管道是文件】
【看成文件,就可以用read和write】
原型:
#include <unistd.h>
int pipe(int pipefd[2]);
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
当一个管道建立时,会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。
需要关闭管道,只需要把这两个文件描述符关闭即可。
管道在内核中开辟空间被创建。
注:
read函数,没有数据,会阻塞。
无名管道通信前,需要一方关闭读,另一方关闭写。
read 没有 阻塞 像一个水管
代码:
管道编程,读时候关闭写,写时候关闭读。这个有点残疾,
当管道中没有数据时候,read函数会阻塞,直到父进程写入数据,子进程继续执行。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int fd[2];
char buf[128];
int pid;
//int pipe(int pipefd[2]);
if( pipe(fd) == -1)
{
printf("creat pipe failed\n");
}
if( (pid = fork()) < 0)
{
printf("create pro failed\n");
}
else if (pid == 0)
{
printf("this is chlid pro\n");
close(fd[1]);
//ssize_t read(int fd, void *buf, size_t count);
read(fd[0], buf, sizeof(buf));
printf("child read:%s\n", buf);
exit(0);
}
else
{//父进程
sleep(3);//这3秒 子进程阻塞
printf("this is parent pro\n");
close(fd[0]);
//ssize_t write(int fd, const void *buf, size_t count);
write(fd[1], "from father to child", sizeof("from father to child"));
wait(NULL);
}
return 0;
}
读和写一开始就确定,一般不修改,无名管道存储在内存中,非磁盘。
这是管道,无名管道。
下面说命名管道。
二、命名管道
FIFO
FIFO,也称命名管道,它是一种文件类型。
特点
- FIFO可以在无关的进程间交换数据,与无名管道不同。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
原型
在man手册第三页
#include <sys/types.h>
#include <sys/stat.h>
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。
RETURN VALUE
On success mkfifo() and mkfifoat() return 0. In the case of an error, -1 is returned (in
which case, errno is set appropriately).
例子
调用mkfifo
p类型文件 、可读可写权限
mkfifo的返回值
RETURN VALUE
成功返回0,失败返回-1。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int ret = mkfifo("./file", 0600);
if(ret == 0){
printf("mkfifo success\n");
}
if(ret == -1){
printf("mkfifo failed\n");
}
return 0;
}
修整一下返回值问题:成功失败都显示
命名管道文件已存在的情况
如下程序,当文件存在时,显示创建失败和失败原因。文件不存在时,显示创建成功。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if( mkfifo("./file", 0600) == -1 && errno == EEXIST){
printf("mkfifo failed\n");
perror("why:");
}else{
if(errno == EEXIST){
printf("file exist\n");
}else{
printf("mkfifo success\n");
}
}
return 0;
}
不显示文件是否存在信息,显示其它错误信息
上一程序,如遇到文件存在之外的错误,也会输出 mkfifo success。做如下调整。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if( mkfifo("./file", 0600) == -1 && errno != EEXIST){
printf("mkfifo failed\n");
perror("why:");
}
return 0;
}
命名管道数据通信的编程实现
#include <fcntl.h> 是O_RDONLY的库
程序运行,但没动(阻塞)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if( mkfifo("./file", 0600) == -1 && errno != EEXIST){
printf("mkfifo failed\n");
perror("why:");
}
//int open(const char *pathname, int flags);
open("./file", O_RDONLY);
printf("open success\n");
return 0;
}
所以,
//read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
if( mkfifo("./file", 0600) == -1 && errno != EEXIST){
printf("mkfifo failed\n");
perror("why:");
}
//int open(const char *pathname, int flags);
open("./file", O_RDONLY);
printf("open success\n");
return 0;
}
//writedemo6
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
open("./file", O_WRONLY);
printf("open success\n");
return 0;
}
先调用read,会阻塞。这时候,调用write,进程以写打开才继续走
使用fifo读写数据
//demo6.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
char buf[32] = {0};
int fd;
int nread;
if( mkfifo("./file", 0600) == -1 && errno != EEXIST){
printf("mkfifo failed\n");
perror("why:");
}
//int open(const char *pathname, int flags);
fd = open("./file", O_RDONLY);
printf("open success\n");
while(1){
nread = read(fd, buf, sizeof(buf));
printf("read %d bytes from fifo, content: %s\n", nread, buf);
}
close(fd);
return 0;
}
//writedemo6.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);
int main()
{
int fd;
int cnt = 0;
char *str = "i come from writefifo";
fd = open("./file", O_WRONLY);
while(1){
sleep(1);
if(cnt == 5) {
break;
}
write(fd, str, strlen(str));
cnt++;
}
printf("open success\n");
close(fd);
return 0;
}
三、消息队列通信原理
原型及特点
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
原型
/*
key是一个索引值,通过key这个索引值在内核中找到某个队列
flag是打开队列的方式,
返回一个int,是队列ID。
*/
int msgget(key_t key, int msgflg);
/** 添加消息
消息ID 消息 消息大小 标志位
*/
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/* 读取消息
消息ID 消息 消息大小 队列类型 标志位
*/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/* 控制消息队列
消息ID
*/
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
一下两种情况下,msgget将创建一个新的消息队列:
● 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
● key参数为IPC_PRIVATE。
第一种方式比第二种方式常用,因为两个进程同时使用就不能是私有。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
● type == 0,返回队列中的第一个消息;
● type > 0,返回队列中消息类型为 type 的第一个消息;
● type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
消息队列编程收发数据
第一行:如果有队列就直接获取,没有就创建队列
msgrcv最后一个参数为0,是默认,就是读不到数据就阻塞
信息定义类似下面:
//msgGet.c
#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;
//1.get queue
// key是一个索引值,通过key这个索引值在内核中找到某个队列
//int msgget(key_t key, int msgflg);
int msgId = msgget(0x1234, IPC_CREAT|0777);
// key随便写
// 0777权限
if(msgId == -1){
printf("get queue fail\n");
}
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
//第三个参数是结构体中mtext的空间大小
//第四个参数 888 是消息种类,接收、发送两端须一致
//msgrcv最后一个参数为0,是默认,就是读不到数据就阻塞
printf("read from que: %s\n", readBuf.mtext);
return 0;
}
//msgSend.c
#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 quene"};
//1.get queue
int msgId = msgget(0x1234, IPC_CREAT|0777);//通过同样的索引值找到消息队列
if(msgId == -1){
printf("get queue fail\n");
}
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
//最后一个参数为0,是默认,就是读不到数据就阻塞
return 0;
}
两端收的同时也发:
//msgGet.c
#include <string.h>
#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;
//1.get queue
//int msgget(key_t key, int msgflg);
int msgId = msgget(0x1234, IPC_CREAT|0777);
// key随便写
// 0777权限
if(msgId == -1){
printf("get queue fail\n");
}
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
printf("read from que: %s\n", readBuf.mtext);
struct msgbuf sendBuf = {666, "thank your reach"};
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
return 0;
}
//msgSend.c
#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 quene"};
//1.get queue
int msgId = msgget(0x1234, IPC_CREAT|0777);//通过同样的索引值找到消息队列
if(msgId == -1){
printf("get queue fail\n");
}
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
struct msgbuf readBuf;
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 666, 0);
printf("read from que: %s\n", readBuf.mtext);
return 0;
}
键值生成及消息队列移除
ftok
msgctl
ftok和删除消息队列的应用:
//msgGet.c
#include <string.h>
#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;
//1.get queue
key_t key;
key = ftok(".", 1);
printf("key=%x\n", key);
//int msgget(key_t key, int msgflg);
int msgId = msgget(key, IPC_CREAT|0777);
// key随便写
// 0777权限
if(msgId == -1){
printf("get queue fail\n");
}
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
printf("read from que: %s\n", readBuf.mtext);
struct msgbuf sendBuf = {666, "thank your reach"};
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//用IPC_RMID指令 删除该key消息队列
msgctl(msgId, IPC_RMID, NULL);
return 0;
}
#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 quene"};
key_t key;
key = ftok(".", 1);
printf("key=%x\n", key);
//int msgget(key_t key, int msgflg);
int msgId = msgget(key, IPC_CREAT|0777);
if(msgId == -1){
printf("get queue fail\n");
}
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
struct msgbuf readBuf;
msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 666, 0);
printf("read from que: %s\n", readBuf.mtext);
msgctl(msgId, IPC_RMID, NULL);//调用RMID指令删除对应key的消息队列
return 0;
}
四、共享内存
共享内存概述
IPC的方式有几种:
- 无名管道
- 命名管道
- 消息队列
- 共享内存:前四种中,比较先进,使用较多的一种方式。
- 信号
- 信号量
信号量不作为IPC的一种方式,它更像去控制一种临界资源。
管道:男的放入纸条,女的取走纸条。单向,残疾
消息队列:桌子上有箱子,男的把纸条放入箱子,女的看纸条不拿走。女的也能放入纸条,男的能看不拿走。
共享内存:比较高级,桌子上一张纸。男的写完,女的直接就看到。女的写完男的直接看到。
共享内存编程步骤:
- 创建/打开 共享内存
- 映射
- 数据
- 释放共享内存
- 干掉
共享内存编程实现
原型:
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int shmflg);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void shmat(int shmid, const void *shmaddr, int shmflag);
//第一个参数是我们获取共享内存的ID
//第二个参数一般写0让Linux内核自动安排共享内存
//第三个参数一般我们也写0,代表映射进来的共享内存可读可写
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(const void *shmaddr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//第一个参数共享内存是共享内存ID
//第二个参数是指令(可查man手册查看),类似消息队列
//第三个是卸载共享内存时候产生的一些信息,我们不关心这些信息,写NULL
共享内存大小必须以兆为单位。
正常退出 0 异常 -1
一般步骤:
- 创建或者获取
- 映射
- 读/写
- 卸载(断开进程和共享内存的联系)
- 挂掉(挂掉,避免占用空间)
例子:
//shmw.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int shmid;
key_t key;
key = ftok(".", 1);
//int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key, 1024*4, IPC_CREAT|0666);//大小以M为单位
if(shmid == -1){
printf("shmget fail\n");
exit(-1);
}
//获取之后,映射共享内存
//定义一个变量指向共享内存
char *shmaddr;
//void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid, 0, 0);//到此,完成映射
printf("shmat ok\n");
//向共享内存中写数据
strcpy(shmaddr, "changx");
sleep(5);
//使用共享内存完毕,卸载共享内存
//int shmdt(const void *shmaddr);
shmdt(shmaddr);
//挂掉共享内存,免得占用空间
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid, IPC_RMID, 0);
printf("quit\n");
return 0;
}
有了写之后,还需要读。读的时候不需要创建,直接获取,因此,shmget的第三个参数写为0。
读取后,卸载共享内存。写那边有删除,这里就不删除了。
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int shmid;
key_t key;
key = ftok(".", 1);
//int shmget(key_t key, size_t size, int shmflg);
shmid = shmget(key, 1024*4, 0);//大小以M为单位
if(shmid == -1){
printf("shmget fail\n");
exit(-1);
}
//获取之后,映射共享内存
//定义一个变量指向共享内存
char *shmaddr;
//void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmid, 0, 0);//到此,完成映射
printf("shmat ok\n");
printf("data:%s\n", shmaddr);
shmdt(shmaddr);//断开进程和共享内存的联系
//挂掉共享内存,免得占用空间
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmctl(shmid, IPC_RMID, 0);
printf("quit\n");
return 0;
}
如何查看操作系统中有哪些共享内存:
ipcs -m //查看
ipcrm -m shmid(共享内存id) //删除某个id的共享内存
共享内存不支持原子操作(两个进程同时写入,就很容易乱)。可以利用信号量控制。
若是刚创建完共享内存就退出程序,查看操作系统中有时共享内存会多出一个。
五、信号
5.1 信号概述
对于Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为Linux提供了一种处理异步事件的方法。比如,终端用户出入了 ctrl c 来中断程序,会通过信号机制停止一个程序。
单片机的中断,串口有数据,硬件处理中断。
信号的名字和编号
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO”,“SIGCHID”等等。信号定义在 signal.h
中,信号名都定义为正整数。
具体的信号名称可以使用 kill -l
来查看信号名字以及序号,信号是从 1 开始编号的,不存在 0 的编号。kill对于信号 0 有特殊的应用。从应用层看编号是从1到64。
信号的处理:
信号的处理有三种方式,分别是:忽略、捕捉、默认动作。
- 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL 和 SIGSTOP )。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的进程,这显然是内核设计者不希望看到的场景。
- 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号的处理函数,然后将这个函数告诉给内核。当信号产生时,由内核来调用用户自定义的函数,由此来实现某种函数的处理。
- 系统默认动作,对于每个信号来说,系统都有默认的处理动作,当发生了该信号,系统便会自动执行。不对,对于系统来说,多数处理方式比较粗暴,就是直接杀死进程。具体的信号默认动作可以使用
man 7 signal
来查看系统的具体定义。
了解了信号的概述,那么信号是如何使用的呢?
其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。
两种都可以(使用信号编号 或者 使用信号名字)
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段。所谓的实现异步通讯就是信号处理的第二种:捕捉信号。
5.2 信号编程
信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版。
- 入门版:函数 signal
- 高级版:函数 sigaction
信号处理发送函数
信号发送函数也不只一个,同样分为入门版和高级版
- 入门版: kill
- 高级版: sigqueue
#include <signal.h>
// void (*sighandler_t)(int) 是函数指针
// 函数没有返回
// 函数名是 sighandler_t
// 函数的参数是 一个整形参数
typedef void (*sighandler_t)(int);
// 返回值是 sighandler_t类型
// 第一个参数是 要捕捉哪个信号(信号编号,上个图中的,kill -l查询)
// 第二个参数是 上一行代码中的函数指针, (小声:_t是结构体的意思)
sighandler_t signal(int signum, sighandler_t handler);
// handler就是一个函数指针,指向void (*sighandler_t)(int)类型的函数,因为用typedef定义了类型
5.3 信号的绑定
实现 捕捉信号 。键盘按下CTRL C 不能终止程序 没办法停止程序
ctrl+c的默认动作是终止进行,现在我已经捕捉信号,修改默认动作,执行我希望它执行的函数。
#include <signal.h>
#include <stdio.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("\nget signum=%d\n", signum);//打印信号的值
printf("never quit\n");
}
int main()
{
signal(SIGINT, handler);//这样就完成了信号的注册 kill -l查询第一个参数
while(1);
return 0;
}
按CTRL C的效果:不能退出
增加了捕获其它信号,但是SIGKILL是不能捕捉的。
代码如下:
#include <signal.h>
#include <stdio.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("\nget signum=%d\n", signum);//打印信号的i值
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1");
break;
}
//printf("never quit\n");
}
int main()
{
signal(SIGINT, handler);//这样就完成了信号的注册
signal(SIGKILL, handler);
signal(SIGUSR1, handler);
while(1);
return 0;
}
捕获信号的效果:
5.4 信号的发送
通过指令发送信号,能不能通过程序发送信号?能!通过 kill 函数。
kill函数使用如下:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//int kill(pid_t pid, int sig);
//参数一:进程号 参数二:信号编号
int main(int argc, char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);//获取信号编号
pid = atoi(argv[2]);//获取进程号
printf("num=%d, pid=%d\n", signum, pid);
kill(pid, signum);//发送信号
printf("send signal ok");
return 0;
}
效果:
另外一种实现方式:领用system函数调用脚本。实际上就是调用指令嘛。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int signum;
int pid;
char cmd[128] = {0};
signum = atoi(argv[1]);
pid = atoi(argv[2]);
printf("num=%d, pid=%d\n", signum, pid);
sprintf(cmd, "kill -%d %d", signum, pid);//格式化字符串
system(cmd);
//kill(pid, signum);
printf("send signal ok");
return 0;
}
5.5 信号的忽略
KILL是无法忽略的
如何忽略信号?
man手册中有
输入如下
man 2 signal
然后搜索 SIG 开头的
编码:(忽略信号)
#include <signal.h>
#include <stdio.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("\nget signum=%d\n", signum);//打印信号的i值
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1");
break;
}
//printf("never quit\n");
}
int main()
{
signal(SIGINT, SIG_IGN);//这样就完成了信号的注册
signal(SIGKILL, SIG_IGN);//SIG_IGN是忽略该信号的宏
signal(SIGUSR1, SIG_IGN);
while(1);
return 0;
}
下图,按了两个 CTRL+C 没反应,因为代码中我们忽略了该信号。需要注意的是:如果使用kill指令杀死该进程还是能够杀死的,虽然对9的信号编码进行了信号忽略处理,但还是不能杀死该进程。
5.6 高级版本的(能携带消息了)如何携带消息
入门的重点在于发信号的动作,不会携带消息。就比如女朋友在外边敲门,男朋友在室内做出响应。高级的API就相当于,女朋友在敲门的同时也说话(你快给我开门,不然我TM干死你),也就是发送信号的同时也传递了其它的信息。手动狗头!
高级版本如何使用
我们已经成功完成了信号的收发,那为什么还有高级版的出现呢?其实之前的信号存在一个问题就是,虽然发送和接收到了信号,可是总感觉少些什么,既然都已经把信号发送过去了,为什么不能多携带一些其它数据呢?
正因如此,我们需要另外的函数来通过信号传递的过程中,携带一些数据。先看看发送的函数把。
sigaction 的函数原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//第一个参数是信号编码
//第二个参数是结构体 struct sigaction(结构体中:函数指针1 函数指针2 结构体 整型数)
//第三个参数是结构体 struct sigaction 备份原操作
struct sigaction {
void (*sa_handler)(int); // 和signal的第二个参数一样 //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //能额外接收参数 //信号处理程序,能够接受额外数据和sigqueue配合使用
//int 参数:
//siginfo_t * 参数:(pid谁发的,si_int数据,,si_value(int char*)数据)
//void * 参数:为空代表无数据;非空代表有数据;
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 */
}
union sigval {
int sival_int;
void *sival_ptr;
};
信号发送函数
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
//第一个参数:发给谁
//第二个参数:发的是什么信号
//第三个参数:消息(类型为 int 或 char *)
union sigval {
int sival_int;
void *sival_ptr;
};
接收信号的代码:
//NiceSignal.c
#include <signal.h>
#include <stdio.h>
//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
void handler(int signum, siginfo_t *info, void *context)
{
//读取:把数据拿出来
printf("get signum:%d\n", signum);
//内容是否有 取决于context是否为空
if(context != NULL){
//需要的话,把pid等其他数据也能拿出来,通过
printf("get data = %d\n", info->si_int);
printf("get data = %d\n", info->si_value.sival_int);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = handler;//接受信号后调用handler来处理信号
act.sa_flags = SA_SIGINFO;//目的:能获取消息
sigaction(SIGUSR1, &act, NULL);//注册信号 参数1:接收那个信号 2想干嘛 3用来备份
while(1);
return 0;
}
发送代码:
//send.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid, signum, value);
printf("done\n");
}
/*
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
//第三个参数 联合体
union sigval {
int sival_int;
void *sival_ptr;
};*/
gcc send.c -o send
gcc NiceSignal.c -o pro
运行pro
查看进程pro的pid
运行send
成功利用程序发送信号
改进版:直接显示pid,无需使用命令查询进程pro的pid
收(捕获)
//NiceSignal.c
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//
void handler(int signum, siginfo_t *info, void *context)
{
//读取:把数据拿出来
printf("get signum:%d\n", signum);
//内容是否有 取决于context是否为空
if(context != NULL){
//需要的话,把pid等其他数据也能拿出来,通过
printf("get data = %d\n", info->si_int);
printf("get data = %d\n", info->si_value.sival_int);
printf("get from pid:%d\n", info->si_pid);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = handler;//接受信号后调用handler来处理信号
act.sa_flags = SA_SIGINFO;//目的:能获取消息
printf("pid:%d\n", getpid());
sigaction(SIGUSR1, &act, NULL);//注册信号 参数1:接收那个信号 2想干嘛 3用来备份
while(1);
return 0;
}
发(发送)
//send.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid, signum, value);
printf("pid:%d, done\n", getpid());
}
六、信号量
6.1 信号量概述(和信号一点关系没有)
信号量(semaphore)与已经介绍过的IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。
- 信号量用于进程间同步,若要在进程间 传递数据需要结合共享内存。
- 信号量基于操作系统的PV操作,进程对信号量的操作都是原子操作。
- 每次对信号量的PV操作不仅限于对信号量值加 1 或减 1 ,而且可以加减任意正整数。
- 支持信号量组。
进程间通信方式:
- 无名管道
- 有名管道
- 消息队列(双向)
- 共享内存(双向)
- 信号
- 信号量(不涉及数据,管理临界资源)
信号量集:Linux下不只是一个信号量
拿锁的过程是 P操作
放回锁的过程是 V操作
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux下的信号量函数都是在通用的信号量组上进行操作,而不是在一个单一的二值信号量上进行操作。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
//控制信号量的相关信息
int semctl(int semid, int semnum, int cmd, ...);
6.2 信号量编程实现一
代码:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
//
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) */
};
int main()
{
key_t key;
int semid;
key = ftok(".", 2);//
//获取/创建信号量 信号量中有一个信号量
semid = semget(key, 1, IPC_CREAT|0666);//key,信号量集中信号量的个数,没有是否创建
union semun initsem;
initsem.val = 1;// 1把锁 有钥匙
//初始化信号量 操作第0个信号量
//SETVAL设置信号量的值,设置为initsem
//int semctl(int semid, int semnum, int cmd, ...);
semctl(semid, 0, SETVAL, initsem);//信号量集的ID,操作第几个信号量,控制信号量集合的方式
int pid = fork();
if(pid > 0){
//拿锁
printf("this is father\n");
//把锁放回去
} else if(pid == 0){
printf("this is child\n");
} else {
printf("fork fail\n");
}
return 0;
}
运行截图:
基本都是父进程先执行,也有子进程先执行。能不能让子进程先执行呢?能!
之前方法:
- 父进程调用wait 等待子进程
- 父进程调用sleep
- 下节使用 PV操作(如何获取/归还信号量)
6.3 信号量编程实现二
目的:让子进程先执行
封装两个函数,P操作和 V操作
取钥匙函数
信号量集有多个信号,需要定义数组。一个信号量就不用定义数组,直接定义变量就可以。
在sem_flg中可以识别的标志是IPC_NOWAIT和SEM_UNDO。如果某个操作指定了SEM_UNDO,那么在进程结束时,该操作将自动撤销。如果是IPC_NOWAIT就失去了意义,因为父子进程执行顺序还不是固定的。
刚开始是无锁的状态,想让谁先执行,谁就执行完才放锁,不需要拿锁。其它进程拿锁,先执行的不放锁,其它进程就不能执行。
这样保证子进程先执行。
代码:
//sem.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//int semget(key_t key, int nsems, int semflg);
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) */
};
//自定义拿锁 P操作
void pGetKey(int id){
//int semop(int semid, struct sembuf *sops, size_t nsops);
//参数一是ID
//参数二是配置的信号量
//参数三是从第二项开始的个数
struct sembuf set;
set.sem_num = 0; //信号编号 /* Operate on semaphore 0 */
set.sem_op = -1; //拿钥匙 -1 /* Wait for value to equal 0 */
set.sem_flg = SEM_UNDO; //等待?????
semop(id, &set, 1);//对从set地址开始的1个信号量操作
printf("getKey\n");
}
//自定义还锁 V操作
void vPutBackKey(int id){
//int semop(int semid, struct sembuf *sops, size_t nsops);
//参数一是ID
//参数二是配置的信号量
//参数三是第二项的个数
struct sembuf set;
set.sem_num = 0; //信号编号 /* Operate on semaphore 0 */
set.sem_op = 1; //放回钥匙
set.sem_flg = SEM_UNDO; //等待?????
semop(id, &set, 1);
printf("put back the Key\n");
}
//主函数入口
int main()
{
key_t key;
int semid;
key = ftok(".", 2);//
//获取/创建信号量 信号量中有一个信号量
semid = semget(key, 1, IPC_CREAT|0666);//key,信号量集中信号量的个数,没有是否创建
union semun initsem;
initsem.val = 0;// 不放锁 默认先执行的子进程已经拿到锁,目的是让子进程先执行
//初始化信号量 操作第0个信号量
//SETVAL设置信号量的值,设置为initsem
//int semctl(int semid, int semnum, int cmd, ...);
semctl(semid, 0, SETVAL, initsem);//信号量集的ID,操作第几个信号量,控制信号量集合的方式
int pid = fork();
if(pid > 0){
//拿锁
pGetKey(semid);
printf("this is father\n");
vPutBackKey(semid);
//把锁放回去
//销毁锁
semctl(semid, 0, IPC_RMID);
} else if(pid == 0){
//pGetKey(semid);
printf("this is child\n");
vPutBackKey(semid);
} else {
printf("fork fail\n");
}
return 0;
}
效果: