目录
信号通信
信号动作的改写
测试
信号的发送
消息队列
消息队列创建要用到的函数
send.c:
recv.c
控制消息队列
信号通信
信号通信是一种在 Unix 和类 Unix 系统(如 Linux)中用于进程间异步通知的机制。信号是一种软件中断,用于通知进程某个特定事件的发生。
软件中断是一种由软件指令触发的中断机制。与硬件中断(由硬件设备如定时器、外部设备触发)不同,软件中断是通过执行特定的指令来请求 CPU 暂停当前的执行流程,转而执行特定的中断处理程序。(关于软件中断在系列文章操作系统内核中会详细讲解)
两个
killall -信号值 进程名
kill -信号值 进程pid
信号值如下图所示
信号动作的改写
信号处理函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:需要注册的信号值
handler:信号处理函数
失败:返回 SIG_ERR
void (*sighandler_t)(int) 回调函数名
测试
注册一个 ctrl c 信号的处理函数,让用户 ctrl c 无法结束当前进程
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 自定义的SIGINT信号处理函数
void sigint_handler(int sig) {
printf("你按下了Ctrl + C,但这个操作不会结束进程\n");
}
int main() {
// 注册SIGINT信号的处理函数
signal(SIGINT, sigint_handler);
while (1) {
printf("进程正在运行...\n");
sleep(1);
}
return 0;
}
signal(SIGINT, sigint_handler)语句将sigint_handler函数注册为SIGINT信号的处理函数。
当用户按下Ctrl + C时,会调用sigint_handler函数,而不是默认的终止进程的操作。
信号的发送
发送一个信号到一个进程
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid:pid进程号
sig:想要发送的信号
返回值: 0 发送成功
-1 发送错误
设置 seconds 秒后发送一个 SIGALRM 信号给当前进程
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
seconds:秒数
systemv通信
消息队列、共享内存和信号量被统称为system-V IPC,V是罗马数字5,是 Unix的AT&T分支的其中一个版本,一般习惯称呼他们为IPC对象,这些对象的操作接口都比较类似,在系统中他们都使用一种叫做key的键值来唯一标识,而且他们都是“持续性”资源--即他们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。
消息队列
对比消息队列,管道这种通信机制的一个弊端是:你无法在管道中读取一个指定的数据,因为这些数据没有做任何标记,读者进程只能按次序地挨个读取,因此多对进程之间的相互通信,除非使用多条管道分别处理,否则无法使用一条管道来完成。
消息队列的基础命令:
查看:
查看消息队列:ipcs -q
查看共享内存:ipcs -m
查看信号量:ipcs -s
查看所有的 IPC对象:ipcs -a
删除:
删除指定的消息队列:ipcrm -q MSG_ID 或者 ipcrm -Q msg_key
删除指定的共享内存:ipcrm-m SHM_ID 或者 ipcrm -M shm_key
删除指定的信号量:ipcrm -s SEM_ID 或者 ipcrm -S sem_key
消息队列创建要用到的函数
通过路径名和进程id生成一个systemv密钥
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname:当前系统存在的一个路径名
proj_id:一个大于 0 的8位数据
返回值: 成功 返回一个密钥
失败 -1
ftok的典型实现是调用stat函数,然后组合以下三个值:
① pathname所在的文件系统的信息(stat结构的st_dev成员)。
② 该文件在本文件系统内的索引节点号(stat结构的st_ino成员)。
③ proj_id的低序8位(不能为0)。
上述三个值的组合产生一个32位键。
ftok返回的是根据文件(pathname)信息和计划编号(proj_id)合成的IPC key键值,返回值的后四位是文件在stat结构体中的成员——文件索引号的的后四位,键值的正数第三第四位是stat结构体中普通文件所在存储器的设备号的后两位,第一第二位是ftok函数自带参数proj_id(计划参数)转换成16进制数的后两位,经测试发现,proj_id转换成二进制时第八位不能为1,如果为1则会创建密钥失败,比如128-255、384-511全都会创建失败,应该是由于二进制第八位表示的是符号位导致的。
备注:
在ftok函数的使用中,如果pathname指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新的节点信息,就会导致相同参数的ftok返回的键值是不一样的,那此时访问到的对象就会不一样,然后系统也不会报错。
参考:linux进程间通信--消息队列相关函数(ftok)详解-CSDN博客
获取一个 system5 的消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key: 密钥
msgflg: 权限标记
IPC_CREAT 创建
IPC_CREAT|IPC_EXCL 检查是否存在
返回值: 成功 消息队列对象ID
失败 -1
发送一个消息到消息队列中
#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);
msqid:消息队列对象id
msgp:消息结构体 (由用户自定义)
msgsz:消息的真实大小
msgtyp:获取的消息的编号
msgflg:消息属性,控制消息发送的方式,有阻塞和非阻塞(IPC_NOWAIT)两种方式,默认为 0
返回值: 成功 0
失败 -1
用法:
消息队列是一种先进先出的队列型数据结构,实际上是系统内核中的一个内部链表。消息被顺序插入队列中,其中发送进程将消息添加到队列末尾,接受进程从队列头读取消息。
多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除。
参考:消息队列(定义、结构、如何创建、消息队列的发送与接收、发送与接收实例)_vc中如何定义使用消息队列-CSDN博客
//定义一个消息结构体
struct msgbuf
{
long mtype; /* 消息的编号, must be > 0 */
char mtext[1024]; /*消息的内容,长度自定义 */
};
mtype指定了消息类型,为正整数。
引入消息类型之后,消息队列在逻辑上由一个消息链表转化为多个消息链表。发送进程仍然无条件把消息写入队列的尾部,但接收进程却可以有选择地读取某个特定类型的消息中最接近队列头的一个,即使该消息不在队列头。相应消息一旦被读取,就从队列中删除,其它消息维持不变。
成员mtext指定了消息的数据。我们可以定义任意的数据类型甚至包括结构来描述消息数据。
//创建一个消息
struct msgbuf msg = {1, "hello"};
//把消息发送到消息队列中
msgsnd(msgid, &msg, 5, 0);
//创建一个消息缓存区
struct msgbuf buf = {0};
//获取消息
msgrcv(msgid,&buf,sizeof(buf),1,0);
创建消息队列并从消息队列中接收消息
send.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
//1.创建密钥
key_t key = ftok("/home",123);
if(key < 0)
{
perror("创建密钥失败\n");
return -1;
}else
{
printf("创建密钥成功\n");
}
//2.通过密钥创建消息队列
int msgid = msgget(key,IPC_CREAT|0777);
if(msgid < 0)
{
perror("创建消息队列失败\n");
return -1;
}else
{
printf("创建消息队列成功\n");
}
//3.定义一个消息结构体
struct msgbuf
{
long mtype; /* 消息的编号, must be > 0 */
char mtext[1024]; /*消息的内容,长度自定义 */
};
//4.创建一个消息
//1是消息的编号,消息队列在内核中以链表的形式储存,同时填1就按照先进先出的队列顺序进行排列
//消息的编号可以改变,改变之后原理就是根据编号在一个消息队列对象中创建多个链表可以根据消息编号直接访问到想要访问的数据
struct msgbuf msg = {1, "hello"};
struct msgbuf msg1 = {1, "world"};
struct msgbuf msg2 = {1, "1234"};
struct msgbuf msg3 = {1, "5678"};
struct msgbuf msg4 = {1, "91011"};
struct msgbuf msg5 = {1, "121314"};
//5.把消息发送到消息队列中
msgsnd(msgid, &msg, 5, 0);
msgsnd(msgid, &msg1, 5, 0);
msgsnd(msgid, &msg2, 4, 0);
msgsnd(msgid, &msg3, 4, 0);
msgsnd(msgid, &msg4, 5, 0);
msgsnd(msgid, &msg5, 6, 0);
}
recv.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
//1.创建密钥
key_t key = ftok("/home",123);
if(key < 0)
{
perror("创建密钥失败\n");
return -1;
}else
{
printf("创建密钥成功\n");
}
//2.通过密钥创建消息队列
int msgid = msgget(key,IPC_CREAT|0777);
if(msgid < 0)
{
perror("创建消息队列失败\n");
return -1;
}else
{
printf("创建消息队列成功\n");
}
//3.定义一个消息结构体
struct msgbuf
{
long mtype; /* 消息的编号, must be > 0 */
char mtext[1024]; /*消息的内容,长度自定义 */
};
//4.创建一个消息缓存区
struct msgbuf buf = {0};
//5.获取消息
msgrcv(msgid,&buf,sizeof(buf),1,0);//1是消息的编号,即使是一个消息队列对象中也可以获得指定的消息
printf("recv:%s\n",buf.mtext);
}
控制消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid:消息队列id
cmd :控制命令 (IPC_RMID) 删除
buf :消息队列的信息 ,(NULL) 不采集
删除一个消息队列:
msgctl(msgid,IPC_RMID,NULL);
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>
int main()
{
//1.创建密钥
key_t key = ftok("/home",123);
if(key < 0)
{
perror("创建密钥失败\n");
return -1;
}else
{
printf("创建密钥成功\n");
}
//2.通过密钥创建消息队列
int msgid = msgget(key,IPC_CREAT|0777);
if(msgid < 0)
{
perror("创建消息队列失败\n");
return -1;
}else
{
printf("创建消息队列成功\n");
}
//3.删除消息队列
msgctl(msgid,IPC_RMID,NULL);
}