信号是一种异步通信方式
同步通信
同步指的是当进程发起一个请求,但是该请求并未马上响应,则进程就会阻塞等待,直到请求被响应
异步通信
异步指的是当进程发起一个请求,如果该请求并未马上响应,则进程会继续执行其他的任务,过来一段时间请求得到了响应,则会通知该进程,该进程得到通知再去对请求做出处理。
一个信号发给一个进程时,操作系统会中断收到信号的进程(如果此进程此时执行的是非原子性操作)。
1~ 31的信号为普通信号,编号为34~64的信号为实时信号
以ctrl+c为例
ctrl+c 后,键盘产生一个硬件中断
操作系统会解释为sigint信号,然后记录在PCB
此时CPU暂停用户空间代码,执行内核空间的硬件中断。硬件中断执行完后CPU返回用户空间,cpu发现进程pcb中的信号为sigint,则会把进程切换到终止态。
由系统硬件检测
kill()
可向指定进程发信号
raise()
只能向当前进程发信号
可以在shell里直接kill
根据软件条件产生信号
例如 sigalrm
进程接收到某个指定信号之前,先设计好该信号响应函数,并把该信号和该响应接口进行关联,这样当进程接收到信号之后,就不会执行信号的默认响应动作,而是执行用户指定的响应动作。
sighandler_t signal(int signum, sighandler_t handler);
signum 是目标信号编号
handler 是信号处理函数的地址
sighandler_t 为void (*sighandler_t)(int)
函数指针
当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。
练习:用户设计两个程序,要求进程A中自定义信号SIGUSR1的响应接口,要求进程B每隔一段时间向进程A就发送SIGUSR1信号,测试进程A是否可以执行关联的响应接口。
#include
#include
#include
#include
#include
#include
#include
#include
void signal_handler(int sigum)
{
switch (sigum) // 用户设计两个程序,要求进程A中自定义信号SIGUSR1的响应接口,
{
case SIGUSR1:
printf("Received SIGUSR1 signal\n");
/* code */
break;
case SIGUSR2:
printf("Received SIGUSR2 signal\n");
/* code */
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
pid_t pid = fork(); // 创建子进程
if(pid==-1)
{
perror("Failed to create child process");
return EXIT_FAILURE; // 子进程创建失败
}
else if(pid>0)
{
// 父进程:每隔一段时间发送SIGUSR1信号给子进程
while(1)
{
sleep(2); // 每隔2秒发送一次信号
kill(pid, SIGUSR1); // 向子进程发送SIGUSR1信号
printf("Parent process sent SIGUSR1 signal to child process\n");
sleep(2);
kill(pid, SIGUSR2); // 向子进程发送SIGUSR1信号
printf("Parent process sent SIGUSR2 signal to child process\n");
}
}
else
{
signal(SIGUSR1,signal_handler);
signal(SIGUSR2,signal_handler);
// 子进程:等待接收信号
while(1)
{
pause(); // 等待信号到来
}
}
//signal(SIGINT,SIG_IGN); // 忽略Ctrl+C信号
return EXIT_SUCCESS;
}
忽略信号指的是当进程接收到某个信号后,并不打算执行该信号的相关动作,而选择直接丢弃该信号。用户可以通过调用signal()函数,只不过函数的第二个参数设置为SIG_IGN即可。
sigprocmask()
接口设置信号集的属性
用户需要创建信号集,并添加相关信号到信号集
阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
练习:根据阻塞接口,设计一个程序,要求把快捷键Ctrl+C的对应信号进行阻塞,需要创建一个信号集,把该信号添加到信号集,对信号集属性进行设置(阻塞),然后测试发送该信号是否会被进程响应。
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
sigset_t set;
sigaddset(&set, SIGINT); // 添加Ctrl+C对应的信号到信号集
sigprocmask(SIG_BLOCK, &set, NULL); // 阻塞信号集中的信号
printf("Ctrl+C is blocked. Press Ctrl+C to test.\n");
while(1);
}
就是当进程没被调度运行时,发送来的信号会被放入一个信号集,里面存储了待处理的信号,当进程再次被调度时,这些信号才会被处理。
消息队列(Message Queue)是一种用于在分布式系统中实现异步通信的中间件技术。它允许生产者(发送方)将消息发送到队列,消费者(接收方)从队列中异步获取并处理消息。
一个消息队列只能存在一种数据类型
操作系统为每个消息队列分配唯一键值,进程可以用键值指定消息队列来发消息。
msgget()
可以创建或者打开一个消息队列
shell 指令 ipcmk 可以创建 IPC 对象
int msgget(key_t key, int msgflg)
其中 key 是要创建消息队列的key键值,msgflg是消息队列的标志, IPC_CREAT 如果不存在则创建,IPC_EXCL 指如果存在则调用失败。msgget() 成功返回 标识符,失败返回-1 并设置错误码
msgget()的key可以用户自定义,但是常用
ftok()
生成IPC对象的键值key
key_t ftok(const char *pathname, int proi_id)
pathname指的是系统中已经存在并且可以访问的一个文件的路径
第二个参数指的是项目ID,这个可以由用户进行指定
练习:设计程序,在Linux系统中创建一个消息队列,并测试消息队列的键值key的组成是否正确。提示:可以通过stat()函数获取文件的属性信息。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
key_t key = ftok(".", 0xffffff01); // 生成一个唯一的键值
int msg_id = msgget(key, IPC_CREAT); // 创建消息队列
if(msg_id == -1) {
perror("msgget failed");
exit(EXIT_FAILURE);
}
printf("Message queue created with ID: %#x\n", msg_id);
struct stat file_stat;
stat(".", &file_stat); // 获取文件状态信息
printf("%#lx\n",file_stat.st_dev);
printf("%#lx\n",file_stat.st_ino);
printf("0xffffff01\n");
u_int32_t expected_key = ((0xffffff01 & 0xFF) << 24)|
((file_stat.st_dev & 0xFF)<< 16) |
(file_stat.st_ino & 0xFFFF) ;
printf("0x%08x\n",expected_key);
printf("0x%08x\n",key);
return 0;
}
msgsnd
函数,用于向指定消息队列发送消息,
msgrcv
函数,利用该接口从指定消息队列读消息
发消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf{
long mtype; //消息类型
char mtext[1]; //消息正文
}
读取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容。
/*练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容。*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FILE_PATH "msgq_key_file"
#define PROJ_ID 65 // 'A'
#define MSG_TYPE 1
struct msgbuf {
long mtype;
pid_t pid;
};
int msqid = -1;
pid_t pid_a, pid_b;
void sigusr1_handler(int sig) {
// 进程B:收到SIGUSR1,打开消息队列并写入PID
key_t key = ftok(FILE_PATH, PROJ_ID);
if (key == -1) {
perror("Process B: ftok");
exit(EXIT_FAILURE);
}
msqid = msgget(key, 0666);
if (msqid == -1) {
perror("Process B: msgget");
exit(EXIT_FAILURE);
}
printf("Process B: Opened message queue with ID: %d\n", msqid);
// 写入PID
struct msgbuf msg;
msg.mtype = MSG_TYPE;
msg.pid = getpid();
if (msgsnd(msqid, &msg, sizeof(pid_t), 0) == -1) {
perror("Process B: msgsnd");
exit(EXIT_FAILURE);
}
printf("Process B: Sent PID %d to message queue\n", msg.pid);
// 向进程A发送SIGUSR2
printf("Process B: Sending SIGUSR2 to Process A (PID: %d)\n", pid_a);
if (kill(pid_a, SIGUSR2) == -1) {
perror("Process B: kill");
exit(EXIT_FAILURE);
}
}
void sigusr2_handler(int sig) {
// 进程A:收到SIGUSR2,读取消息
struct msgbuf msg;
ssize_t bytes_read = msgrcv(msqid, &msg, sizeof(pid_t), MSG_TYPE, 0);
if (bytes_read == -1) {
perror("Process A: msgrcv");
exit(EXIT_FAILURE);
}
printf("Process A: Received message with PID: %d\n", msg.pid);
}
int main() {
// 注册信号处理函数(在fork前设置,避免竞争)
if (signal(SIGUSR1, sigusr1_handler) == SIG_ERR ||
signal(SIGUSR2, sigusr2_handler) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
// 创建用于ftok的文件
FILE *fp = fopen(FILE_PATH, "w");
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
fclose(fp);
// 生成键值
key_t key = ftok(FILE_PATH, PROJ_ID);
if (key == -1) {
perror("ftok");
unlink(FILE_PATH);
exit(EXIT_FAILURE);
}
// 创建消息队列
msqid = msgget(key, IPC_CREAT | 0666);
if (msqid == -1) {
perror("msgget");
unlink(FILE_PATH);
exit(EXIT_FAILURE);
}
printf("Process A: Message queue created with ID: %d\n", msqid);
// 获取进程A的PID
pid_a = getpid();
// 创建进程B
pid_b = fork();
if (pid_b == -1) {
perror("fork");
msgctl(msqid, IPC_RMID, NULL);
unlink(FILE_PATH);
exit(EXIT_FAILURE);
}
if (pid_b == 0) {
// 子进程(进程B)
printf("Process B: Started with PID %d\n", getpid());
// 等待SIGUSR1信号
pause();
exit(0); // 进程B完成任务后退出
} else {
// 父进程(进程A)
printf("Process A: Created Process B with PID %d\n", pid_b);
// 向进程B发送SIGUSR1
sleep(1); // 确保进程B已设置信号处理
printf("Process A: Sending SIGUSR1 to Process B (PID: %d)\n", pid_b);
if (kill(pid_b, SIGUSR1) == -1) {
perror("Process A: kill");
msgctl(msqid, IPC_RMID, NULL);
unlink(FILE_PATH);
exit(EXIT_FAILURE);
}
// 等待SIGUSR2信号
pause();
// 清理消息队列
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("Process A: msgctl");
} else {
printf("Process A: Message queue removed\n");
}
// 清理文件
if (unlink(FILE_PATH) == -1) {
perror("unlink");
}
// 等待进程B结束
wait(NULL);
printf("Process A: Process B terminated\n");
}
return 0;
}
IPC对象是需要手动删除的
ipcrm {shm|msg|sem} id
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages in queue */
msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
意思是多个进程共享物理内存中的同一段内存,
提高访问效率。
shmget()
函数用于向内核申请物理内存
#include
#include
int shmget(key_t key, size_t size, int shmflg);
key与消息队列类似,可以使用ftok()生成唯一键值key
size为共享内存段大小
shmflg 标志:IPC_CREATE如果共享内存不存在则创建,IPC_EXCL如果存在则调用失败。
返回结果:成功则返回标识符,失败返回-1
练习:根据函数的使用规则,在系统中创建一块共享内存,创建完成后,要求在程序中执行指令 ipc -s 来查看当前系统中IPC对象的资源情况。
申请成功后是无法直接访问的,
需要通过
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);//映射
int shmdt(const void *shmaddr); //解除映射
参数 shmid 是共享内存的标识符
shmaddr一般填NULL ,由内核分配
练习:设计三个程序,要求三个程序申请一块共享内存,并分别映射到各自进程的地址空间,进程A和进程B对共享内存段中的数据进行修改,然后进程C不断输出共享内存段中的数据,并观察效果,要求实现进程间的互斥,避免竞争。提示:信号
shmctl()
可以用来 设置、获取共享内存属性;删除共享内存。
多个进程访问同个资源时会出现竞争
这个资源叫 临界资源;这个进程中访问这个资源的位置叫做临界区
申请称为 P操作 归还称为 V操作
P操作
每次P操作,会减一
V操作
每次V操作,会加一
对信号量申请和释放的前提是,必须创建信号量,
Linux提供了一个semget()
函数,可以创建或打开信号量
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
设计程序,在程序中创建一个信号量集,要求信号量集中有一个信号量,创建完成后执行指令 ipcs -a的指令,输出当前系统中IPC对象的信息。
#include
#include
#include
int semop(int semid, struct sembuf *sops, size_t nsops);
int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);
semop
可以控制对信号量的申请或释放
参数
struct sembuf
{
unsigned short sem_num;
short sem_op;
short sem_flg;
};
- sem_num 是信号量元素的下标,
- sem_op 对选中信号量的操作,
- sem_flg 对选中的信号量的操作标志,常见的标志有IPC_NOWAIT和SEM_UNDO
P操作会在实际数量小于申请数量时阻塞
V操作不会阻塞。
练习:设计一个程序,作为进程A,进程A专门创建一个信号量集,要求信号量集中有1个信号量,对信号量集合中的信号量进行设置,要求集合中的信号量的初值为1,然后再设计2个程序,分别是进程B和进程C,要求进程B和进程C使用进程A创建的信号量集合中的信号量实现互斥访问。 提示:进程A、进程B、进程C需要使用共享内存作为临界资源的访问。
共享头文件
负责定义key、结构体、P/V操作实现
// sem.h
#ifndef SEM_H
#define SEM_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SEM_KEY 0x1234
#define SHM_KEY 0x5678
#define SHM_SIZE 128
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// 封装P操作(等待)
void sem_wait(int semid) {
struct sembuf sb = {0, -1, 0};
if (semop(semid, &sb, 1) == -1) {
perror("sem_wait failed");
exit(1);
}
}
// 封装V操作(释放)
void sem_signal(int semid) {
struct sembuf sb = {0, 1, 0};
if (semop(semid, &sb, 1) == -1) {
perror("sem_signal failed");
exit(1);
}
}
#endif
进程A
创建信号量并初始化、创建共享内存
// processA.c
#include "sem.h"
int main() {
// 创建信号量集(1个信号量)
int semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget failed");
exit(1);
}
// 初始化信号量为1
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl failed");
exit(1);
}
printf("Semaphore created and initialized to 1.\n");
// 创建共享内存
int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
printf("Shared memory created.\n");
return 0;
}
进程B
通过P/V操作,对共享内存获得控制权
// processB.c
#include "sem.h"
int main() {
int semid = semget(SEM_KEY, 1, 0666);
int shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (semid == -1 || shmid == -1) {
perror("semget or shmget failed");
exit(1);
}
char *shm = (char *)shmat(shmid, NULL, 0);
for (int i = 0; i < 5; ++i) {
sem_wait(semid);
snprintf(shm, SHM_SIZE, "Process B writes %d", i);
printf("Process B wrote to shared memory: %s\n", shm);
sleep(1); // 模拟处理时间
sem_signal(semid);
sleep(1);
}
shmdt(shm);
return 0;
}
进程C
// processC.c
#include "sem.h"
int main() {
int semid = semget(SEM_KEY, 1, 0666);
int shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (semid == -1 || shmid == -1) {
perror("semget or shmget failed");
exit(1);
}
char *shm = (char *)shmat(shmid, NULL, 0);
for (int i = 0; i < 5; ++i) {
sem_wait(semid);
snprintf(shm, SHM_SIZE, "Process C writes %d", i);
printf("Process C wrote to shared memory: %s\n", shm);
sleep(1); // 模拟处理时间
sem_signal(semid);
sleep(1);
}
shmdt(shm);
return 0;
}