Linux内核、线程、进程同步互斥方法及IPC方法的总结

前段实践在B站进行模拟面试时发现,
模拟面试第四期-已经拿到大厂OFFER的研究生大佬-LINUX卷到飞起
自己对Linux中的同步互斥方法,以及IPC方法,没有很好的理解和总结过。因此,本笔记将总结这部分内容。

内核线程进程
机制原子操作、自旋锁、信号量、互斥量互斥量、读写锁、条件变量、信号量(有些书把信号量放在IPC通信里面,因为它可以被用于进程间通信同步)IPC:管道(无名管道)、FIFO(有名管道)、消息队列、信号(Signals)、共享内存、套接字 (Sockets)
特点提供了多种用于进程间通信和线程同步的基础设施协调同一进程中不同线程对共享数据的访问在不同的进程之间传递数据

1.内核解决并发与竞态的方法

这个部分在Linux内核学习笔记中,已经做过关于内部实现的笔记。

Linux驱动-同步互斥与原子变量 - 认真学习的小诚同学

Linux驱动-内核锁的介绍与使用 - 认真学习的小诚同学

Linux驱动-内核自旋锁spinlock的实现 - 认真学习的小诚同学

Linux驱动-内核信号量semaphore的实现 - 认真学习的小诚同学

Linux驱动-内核互斥量mutex的实现 - 认真学习的小诚同学

这里做个总结:

内核锁分为

  • 自旋锁(无法获得锁时,当前线程原地等待):原始自旋锁(raw_spinlock_t)、位自旋锁(bit spinlocks)
  • 睡眠锁(无法获得锁时,当前线程就会休眠):互斥量、实时互斥锁、信号量、读写信号量等。
机制实现方式
自旋锁UP(单CPU):支持抢占的系统,关闭调度器;不支持抢占的系统,本身就独占了
SMP(多核):使用原子操作ldrex和strex完成互斥
信号量通过自旋锁lock和计数量count,以及一个等待队列(阻塞的线程放在其中)
互斥量同样通过自旋锁lock和二值计数量count,以及一个等待队列(阻塞的线程放在其中)
实现了一个快速路径fastpath-在大部分情况下都可以直接在此获得或释放锁,所以互斥量比信号量的效率高

2.线程间同步

线程同步(synchronization)是指在一定的时间内只允许某一个线程访问某个共享资源。而在此时间内,不允许其它的线程访问该资源。

2.1 互斥量

从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。

2.1.1 API
功能API描述
创建int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);返回:若成功返回0,否则返回错误编号
销毁int pthread_mutex_destroy(pthread_mutex_t mutex);返回:若成功返回0,否则返回错误编号
加锁int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
返回:若成功返回0,否则返回错误编号;
调用pthread_mutex_lock,如果互斥量已经上锁, 调用线程阻塞直到互斥量被解锁。 如果不希望被阻塞,可以使用pthread_mutex_trylock 尝试加锁,无论成功都会返回。
解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);
2.1.2 死锁问题

死锁详解和解决办法_避免死锁的三种方法-CSDN博客

【并发 bug 和应对 (死锁/数据竞争/原子性违反;防御性编程和动态分析) 南京大学2022操作系统-P8】 【精准空降到 24:22】

死锁只有同时满足以下四个条件才会发生:

  • 互斥:一个资源每次只能被一个进程使用;
  • 持有并等待:一个进程请求资源阻塞时,不释放已获得的资源;
  • 不可剥夺:进程已获得的资源不能被强行剥夺;
  • 环路等待:若干进程之间形成头尾相接的循环等待资源关系。

解决方法就是经典的银行家算法。

2.1.3 示例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define NUM_THREADS 5// 共享资源 
int shared_counter = 0;
// 互斥锁
pthread_mutex_t mutex;void* thread_function(void* arg) {int id = *((int*)arg);free(arg);for (int i = 0; i < 5; i++) {// 加锁pthread_mutex_lock(&mutex);// 访问共享资源int local_counter = shared_counter;printf("Thread %d read shared_counter: %d\n", id, local_counter);local_counter++;printf("Thread %d incremented shared_counter to: %d\n", id, local_counter);// 更新共享资源shared_counter = local_counter;// 解锁pthread_mutex_unlock(&mutex);// 模拟一些其他操作和等待sleep(1);}return NULL;
}int main() {pthread_t threads[NUM_THREADS];// 初始化互斥锁pthread_mutex_init(&mutex, NULL);// 创建线程for (int i = 0; i < NUM_THREADS; i++) {int* id = malloc(sizeof(int));*id = i + 1;pthread_create(&threads[i], NULL, thread_function, id);}// 等待所有线程完成for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}// 销毁互斥锁pthread_mutex_destroy(&mutex);// 打印最后的共享计数器值printf("Final value of shared_counter: %d\n", shared_counter);return 0;
}

2.2 读写锁

与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享

适用场景:读写锁非常适合于对数据结构读的次数远大于写的情况。

2.2.1 API
功能API描述
创建int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);返回:若成功返回0,否则返回错误编号
销毁int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);返回:若成功返回0,否则返回错误编号
加锁int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
返回:若成功返回0,否则返回错误编号;
解锁int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
2.2.2 示例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define NUM_READERS 5
#define NUM_WRITERS 2pthread_rwlock_t rwlock;
int shared_data = 0;void* reader(void* arg) {int id = *((int*)arg);free(arg);while (1) {pthread_rwlock_rdlock(&rwlock);printf("Reader %d: read shared_data = %d\n", id, shared_data);pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}void* writer(void* arg) {int id = *((int*)arg);free(arg);while (1) {pthread_rwlock_wrlock(&rwlock);shared_data++;printf("Writer %d: updated shared_data to %d\n", id, shared_data);pthread_rwlock_unlock(&rwlock);sleep(2);}return NULL;
}int main() {pthread_t readers[NUM_READERS], writers[NUM_WRITERS];pthread_rwlock_init(&rwlock, NULL);for (int i = 0; i < NUM_READERS; i++) {int* id = malloc(sizeof(int));*id = i + 1;pthread_create(&readers[i], NULL, reader, id);}for (int i = 0; i < NUM_WRITERS; i++) {int* id = malloc(sizeof(int));*id = i + 1;pthread_create(&writers[i], NULL, writer, id);}for (int i = 0; i < NUM_READERS; i++) {pthread_join(readers[i], NULL);}for (int i = 0; i < NUM_WRITERS; i++) {pthread_join(writers[i], NULL);}pthread_rwlock_destroy(&rwlock);return 0;
}

2.3 信号量

可以理解为一个计数器,表示当前可用共享资源的数量。

  • 信号量基于操作系统的 PV 操作,对信号量的操作都是原子操作;

  • 信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数;

  • 支持信号量组;

  • 有两套信号量:System V 和 Semaphore

2.3.1 API

<sys/sem.h> - System V 信号量

  1. 特性:

    • 属于System V IPC(进程间通信)。

    • 提供了一组复杂的功能,适合复杂的进程间同步控制。

    • 典型操作包括semget()(获取/创建信号量)、semop()(操作信号量)和semctl()(控制信号量集)。

    • 支持信号量集的概念,可以一次性操作多个信号量,适用于高级同步需求。

    • int semget(key_t key, int num_sems, int sem_flags); // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1

      int semctl(int semid, int sem_num, int cmd, ...); // 控制信号量的相关信息

      int semop(int semid, struct sembuf semoparray[], size_t numops); // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1

  2. 使用场景:

    • 适用于需要细粒度、复杂同步的多进程应用程序。
    • 在需要跨越不同应用程序的持久性IPC时(System V信号量可以在系统重启后仍保持存在,直到显式删除)。
  3. 复杂性:

    • 相对复杂,使用起来需要理解其数据结构和操作指标。
    • 相关数据结构和操作通常需要更复杂的设置和管理。需要使用union semun进行一些配置,这是标准的一个例外之处。

<semaphore.h> - POSIX 信号量

  1. 特性:
    • 属于POSIX标准。
    • 提供了更简单易用的信号量API。典型操作包括sem_init()(初始化信号量)、sem_wait()(等待信号量)、sem_post()(释放信号量)和sem_destroy()(销毁信号量)。
    • 可以是命名信号量或无名信号量。命名信号量可以用于进程间同步,而无名信号量通常用于线程间同步。
  2. 使用场景:
    • 适用于进程间或线程间的简单同步。
    • 易于使用,集成到标准C库中,大多数现代系统都支持。
  3. 复杂性:
    • 易于使用和管理,适合需要简单同步机制的应用。
    • 不支持信号量集,但更适合于一般的多线程和多进程同步场景。
2.3.2 示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <fcntl.h>  // For O_CREATint main() {pid_t pid;sem_t *sem;// 创建一个命名信号量,初始值为0sem = sem_open("/example_semaphore", O_CREAT, 0644, 0);if (sem == SEM_FAILED) {perror("sem_open");exit(EXIT_FAILURE);}pid = fork();if (pid < 0) {perror("fork");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程部分printf("子进程: 正在执行任务...\n");sleep(2); // 模拟某些工作的延迟printf("子进程: 完成任务!\n");// 子进程完成任务后,释放信号量sem_post(sem);// 关闭子进程中的信号量sem_close(sem);exit(EXIT_SUCCESS);} else {// 父进程部分printf("父进程: 等待子进程完成...\n");// 等待子进程完成信号量操作sem_wait(sem);printf("父进程: 收到子进程完成信号,继续执行...\n");// 等待子进程退出wait(NULL);// 关闭父进程中的信号量sem_close(sem);// 移除命名信号量sem_unlink("/example_semaphore");exit(EXIT_SUCCESS);}
}

2.4 条件变量

条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。

主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”.

为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起

2.4.1 API
2.4.2 示例
#include <pthread.h>
#include <stdio.h>#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;pthread_mutex_t mutex;
pthread_cond_t cond;void* producer(void* arg) {for (int i = 0; i < 20; i++) {pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond, &mutex);}buffer[count++] = i;printf("Produced: %d\n", i);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}return NULL;
}void* consumer(void* arg) {for (int i = 0; i < 20; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond, &mutex);}int item = buffer[--count];printf("Consumed: %d\n", item);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}return NULL;
}int main() {pthread_t prod, cons;pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);pthread_join(prod, NULL);pthread_join(cons, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

3.进程间通信

参考:一文搞懂六大进程通信机制原理(全网最详细)

3.1 管道

通常指无名管道。

  • 数据只能在一个方向上流动, 具有固定的读端和写端
  • 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3.1.1 API
#include <unistd.h> 
int pipe(int filedes[2]); 

返回值:若成功则返回0,若出错则返回-1; 说明:由参数filedes返回两个文件描述符:filedes[0]为读而打开;filedes[1]为写而打开。

3.1.2 示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>int main() {int pipefd[2];pid_t pid;char writeMessage[] = "Hello from parent!";char readBuffer[100];// 创建管道,pipefd[0]用于读,pipefd[1]用于写if (pipe(pipefd) == -1) {perror("pipe");exit(EXIT_FAILURE);}// 创建子进程pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {// 子进程执行此块// 关闭写入端close(pipefd[1]);// 从管道中读取数据read(pipefd[0], readBuffer, sizeof(readBuffer));printf("子进程接收到: %s\n", readBuffer);// 关闭读取端close(pipefd[0]);_exit(EXIT_SUCCESS);} else {// 父进程执行此块// 关闭读取端close(pipefd[0]);// 向管道中写入数据write(pipefd[1], writeMessage, strlen(writeMessage) + 1);// 关闭写入端close(pipefd[1]);// 等待子进程完成wait(NULL);exit(EXIT_SUCCESS);}
}

3.2 FIFO

命名管道,它是一种文件类型。

  • FIFO可以在无关的进程之间交换数据,与无名管道不同。
  • FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中
#include <sys/stat.h> 
int mkfifo(const char *pathname, mode_t mode); 

返回值:成功返回0,出错返回-1;

其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

示例:

// 发送进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>#define FIFO_NAME "myfifo"int main() {int fd;char message[] = "Hello from FIFO sender!";// 创建FIFO,如果已经存在则不会重新创建if (mkfifo(FIFO_NAME, 0666) == -1) {perror("mkfifo");exit(EXIT_FAILURE);}// 打开FIFO以写入fd = open(FIFO_NAME, O_WRONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 写入数据到FIFOwrite(fd, message, strlen(message) + 1);printf("发送: %s\n", message);// 关闭FIFOclose(fd);// 删除FIFOunlink(FIFO_NAME);return 0;
}
// 接受进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>#define FIFO_NAME "myfifo"int main() {int fd;char buffer[100];// 打开FIFO以读取fd = open(FIFO_NAME, O_RDONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 从FIFO读取数据read(fd, buffer, sizeof(buffer));printf("接收: %s\n", buffer);// 关闭FIFOclose(fd);return 0;
}

3.3 消息队列

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

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除;
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
3.3.1 API
#include <sys/msg.h>
int msgget(key_t key, int flag);  // 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf); // 控制消息队列:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag); // 添加消息:成功返回0,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag); // 读取消息:成功返回消息数据的长度,失败返回-1

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

  1. 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREATE
  2. key参数为IPC_PRIVATE

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

  1. type == 0,返回队列中的第一个消息;
  2. type > 0,返回队列中消息类型为 type 的第一个消息;
  3. type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来

3.3.2 示例

服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define QUEUE_KEY 1234
#define MSG_TYPE_REQUEST 1
#define MSG_TYPE_RESPONSE 2struct message {long msg_type;char msg_text[100];
};int main() {int msgid;struct message msg;// 创建消息队列msgid = msgget(QUEUE_KEY, 0666 | IPC_CREAT);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}while (1) {// 等待接收特定类型的消息if (msgrcv(msgid, &msg, sizeof(msg.msg_text), MSG_TYPE_REQUEST, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("服务端接收到: %s\n", msg.msg_text);// 发送反馈消息msg.msg_type = MSG_TYPE_RESPONSE;snprintf(msg.msg_text, sizeof(msg.msg_text), "Acknowledged: %s", msg.msg_text);if (msgsnd(msgid, &msg, sizeof(msg.msg_text), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}}// 删除消息队列(一般情况下,不会到这一步,因为服务端通常持续运行)msgctl(msgid, IPC_RMID, NULL);return 0;
}
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define QUEUE_KEY 1234
#define MSG_TYPE_REQUEST 1
#define MSG_TYPE_RESPONSE 2struct message {long msg_type;char msg_text[100];
};int main() {int msgid;struct message msg;// 获取消息队列msgid = msgget(QUEUE_KEY, 0666);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 准备和发送请求消息msg.msg_type = MSG_TYPE_REQUEST;snprintf(msg.msg_text, sizeof(msg.msg_text), "Hello, Server!");if (msgsnd(msgid, &msg, sizeof(msg.msg_text), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}// 等待接收服务端的反馈消息if (msgrcv(msgid, &msg, sizeof(msg.msg_text), MSG_TYPE_RESPONSE, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("客户端接收到反馈: %s\n", msg.msg_text);return 0;
}

3.4 信号

信号和信号量是完全不同的两个概念

3.4.1 有哪些信号
kill -l // 罗列出系统中的信号1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

Linux 系统中有许多信号,其中前面 31 个信号都有一个特殊的名字,对应一个特殊的事件,这些信号都是从 Unix 系统继承下来的,他们还有个名称叫**“不可靠信号”**,他们有如下的特点:

  • 非实时信号不排队,信号的响应会相互嵌套。
  • 如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃。
  • 每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。
  • 如果进程的挂起信号中含有实时和非实时信号,那么进程优先响应实时信号并且会从大到小依此响应,而非实时信号没有固定的次序。

后面的 31 个信号(从 SIGRTMIN[34]SIGRTMAX[64])是 Linux 系统新增的实时信号,也被称为**“可靠信号”**,这些信号的特征是:

  • 实时信号的响应次序按接收顺序排队,不嵌套。
  • 即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
  • 实时信号没有特殊的系统事件与之对应
    在这里插入图片描述

对以上信号,需要着重注意的是:

1,上表中罗列出来的信号的“值”,在 x86、PowerPC 和 ARM 平台下是有效的,但是别的平台的信号值也许跟这个表的不一致。

2,“备注”中注明的事件发生时会产生相应的信号,但并不是说该信号的产生就一定发生了这个事件。事实上,任何进程都可以使用函数 kill()来产生任何信号。

3,信号 SIGKILLSIGSTOP 是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按缺省动作来响应

换句话说,除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:

A) 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。否则进入B。

B) 如果该信号被捕捉,那么进一步判断捕捉的类型:

​ B1) 如果设置了响应函数,那么执行该响应函数。

​ B2) 如果设置为忽略,那么直接丢弃该信号。

​ 否则进入C。

C) 执行该信号的缺省动作

3.4.2 API
  1. signal 函数
    • 用于设置一个简单的信号处理器。
    • 原型:void (*signal(int sig, void (*func)(int)))(int);
    • 使用时,指定要捕获的信号以及处理信号的函数。
  2. sigaction 函数
    • 提供了更强大和灵活的信号处理设置。
    • 原型:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    • 可以将struct sigactionsa_handler设置为SIG_IGN来忽略信号或SIG_DFL恢复默认处理。
    • 提供了一种可靠的方法来设置信号处理程序,并支持更多功能,如信号阻塞集。
  3. raise 函数
    • 发送信号给调用进程本身。
    • 原型:int raise(int sig);
    • 实际上相当于调用 kill(getpid(), sig);
  4. kill 函数
    • 发送信号给指定进程。
    • 原型:int kill(pid_t pid, int sig);
    • 可以用来向一个或多个进程发送信号。

……

3.5 共享内存

3.5 共享内存

指两个或多个进程共享一个给定的存储区.

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取
  • 因为多个进程可以同时操作,所以需要进行同步
  • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
    在这里插入图片描述
3.5.1 API
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag); // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf); // 控制共享内存的相关信息:成功返回0,失败返回-1
void *shmat(int shm_id, const void *addr, int flag); // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
int shmdt(void *addr); // 断开与共享内存的连接:成功返回0,失败返回-1

当用shmget函数创建一段共享内存时,必须指定其size;而如果引用一个已存在的共享内存,将size 指定为0。

当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,随后可以访问。

shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

3.5.2 示例

【共享内存+信号量+消息队列】:

  1. 消息队列将用于初始化通信。在这种情况下,客户端可以请求服务,同时也可以用来通知服务器有新的请求需要处理。
  2. 共享内存将作为数据交换的主要手段,因为它允许两个进程共享和访问同一内存段。
  3. 信号量用于同步,以便控制对共享内存的访问,防止竞态条件。
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
#define MSG_KEY 0x9abc
#define SHM_SIZE 1024struct message {long msg_type;int client_pid;
};union semun {int val;struct semid_ds *buf;unsigned short *array;
};void handle_client_request(int semid, char *shared_mem, int client_pid) {struct sembuf sb;// Wait (P operation)sb.sem_num = 0;sb.sem_op = -1;sb.sem_flg = 0;if (semop(semid, &sb, 1) == -1) {perror("semop - wait");exit(EXIT_FAILURE);}// 读取客户端的数据printf("服务器读取: %s\n", shared_mem);// 向共享内存中写入处理后的回应snprintf(shared_mem, SHM_SIZE, "服务器已处理请求,收到了: %s", shared_mem);// Signal (V operation)sb.sem_op = 1;if (semop(semid, &sb, 1) == -1) {perror("semop - signal");exit(EXIT_FAILURE);}// 向客户端发送处理完成的通知printf("服务器处理完毕,通知客户端 PID: %d\n", client_pid);
}int main() {int shmid, semid, msgid;char *shared_mem;struct message msg;union semun sem_union;struct sembuf sb;// 创建共享内存shmid = shmget(SHM_KEY, SHM_SIZE, 0666 | IPC_CREAT);if (shmid == -1) {perror("shmget");exit(EXIT_FAILURE);}// 附加共享内存shared_mem = (char *)shmat(shmid, NULL, 0);if (shared_mem == (char *)-1) {perror("shmat");exit(EXIT_FAILURE);}// 创建信号量semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT);if (semid == -1) {perror("semget");exit(EXIT_FAILURE);}// 初始化信号量为1sem_union.val = 1;if (semctl(semid, 0, SETVAL, sem_union) == -1) {perror("semctl");exit(EXIT_FAILURE);}// 创建消息队列msgid = msgget(MSG_KEY, 0666 | IPC_CREAT);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 服务器循环接收请求while (1) {// 接收来自客户端的请求消息if (msgrcv(msgid, &msg, sizeof(int), 1, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("服务器收到来自客户端的请求,PID: %d\n", msg.client_pid);// 处理客户端请求handle_client_request(semid, shared_mem, msg.client_pid);}return 0;
}
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>
#include <unistd.h>#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
#define MSG_KEY 0x9abc
#define SHM_SIZE 1024struct message {long msg_type;int client_pid;
};union semun {int val;struct semid_ds *buf;unsigned short *array;
};int main() {int shmid, semid, msgid;char *shared_mem;struct message msg;union semun sem_union;struct sembuf sb;pid_t pid = getpid();// 获取共享内存shmid = shmget(SHM_KEY, SHM_SIZE, 0666);if (shmid == -1) {perror("shmget");exit(EXIT_FAILURE);}// 附加共享内存shared_mem = (char *)shmat(shmid, NULL, 0);if (shared_mem == (char *)-1) {perror("shmat");exit(EXIT_FAILURE);}// 获取信号量semid = semget(SEM_KEY, 1, 0666);if (semid == -1) {perror("semget");exit(EXIT_FAILURE);}// 获取消息队列msgid = msgget(MSG_KEY, 0666);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 准备发送请求msg.msg_type = 1;msg.client_pid = pid;// Wait (P operation)sb.sem_num = 0;sb.sem_op = -1;sb.sem_flg = 0;if (semop(semid, &sb, 1) == -1) {perror("semop - wait");exit(EXIT_FAILURE);}// 向共享内存中写入请求snprintf(shared_mem, SHM_SIZE, "请求数据来自客户端 PID: %d", pid);printf("客户端发送: %s\n", shared_mem);// Signal (V operation)sb.sem_op = 1;if (semop(semid, &sb, 1) == -1) {perror("semop - signal");exit(EXIT_FAILURE);}// 发送请求消息到服务端if (msgsnd(msgid, &msg, sizeof(int), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}// 等待服务端处理sleep(1); // 这里使用睡眠进行简单的等待,实践中应采用更好的同步机制// Signal again to re-check shared memory// 这里选择重新检入共享内存内容前的信号处理,依赖于应用具体设计// Read the server responseprintf("客户端收到: %s\n", shared_mem);return 0;
}

3.6 套接字

Socket支持不同主机上的两个进程IPC。这里不详细介绍了,可以参考网络编程相关知识。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/462525.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Automated Isotope Identification Algorithm UsingArtificial Neural Networks-论文阅读

Automated Isotope Identification Algorithm Using Artificial Neural Networks 使用人工神经网络的自动同位素识别算法 M.Kamuda, J. Stinnett, and C.J. Sullivan 摘要 需要开发一种算法,以确定包含多种放射性同位素混合物的大量低分辨率伽马射线光谱数据集中放射性同位素…

MybatisPlus - 核心功能

文章目录 1.MybatisPlus实现基本的CRUD快速开始常见注解常见配置 2.使用条件构建造器构建查询和更新语句条件构造器自定义SQLService接口 官网 MybatisPlus无侵入和方便快捷. MybatisPlus不仅仅可以简化单表操作&#xff0c;而且还对Mybatis的功能有很多的增强。可以让我们的开…

数据结构:并查集

并查集的原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个 单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一 个元素归属于那个集合的运算。适合于描述这类…

ChatGPT新体验:AI搜索功能与订阅支付指南

就在凌晨&#xff0c;在ChatGPT迎来两周岁生日之际&#xff0c;OpenAI重磅发布了ChatGPT的全新人工智能搜索体验。 期待已久的时刻终于到来&#xff0c; ChatGPT正式转型成为一款革命性的AI搜索引擎! 先来看看ChatGPT搜索&#xff1a;这次不是简单的加个搜索框&#xff0c;而…

JS | 如何更好地优化 JavaScript 的内存回收?

目录 一、理解JavaScript内存生命周期 ● 创建对象和分配内存 ● 内存的使用 ● 内存回收 二、减少内存泄露 ● 避免全局变量 ● 正确使用闭包 三、合理管理内存 ● 局部变量和即时函数 ● 解绑事件监听器 四、使用现代JavaScript特性辅助内存回收 ● 使用WeakMap和…

群控系统服务端开发模式-应用开发-上传配置功能开发

下面直接进入上传配置功能开发&#xff0c;废话不多说。 一、创建表 1、语句 CREATE TABLE cluster_control.nc_param_upload (id int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 编号,upload_type tinyint(1) UNSIGNED NOT NULL COMMENT 上传类型 1&#xff1a;本站 2&a…

Cisco Packet Tracer 8.0 路由器的基本配置和Telnet设置

文章目录 构建拓扑图配置IP地址配置路由器命令说明测试效果 构建拓扑图 1&#xff0c;添加2811路由器。 2&#xff0c;添加pc0。 3&#xff0c;使用交叉线连接路由器和pc&#xff08;注意线路端口&#xff09;。 4&#xff0c;使用配置线连接路由器和pc&#xff08;注意线路…

从气象中心采集cma台风路径数据

在自然灾害监测与预警领域&#xff0c;台风作为一种极具破坏力的自然现象&#xff0c;其路径预测和强度评估对于减少潜在损失至关重要。随着互联网技术的发展&#xff0c;国家气象中心等专业机构提供了详尽的台风历史数据和实时跟踪服务&#xff0c;通过网络接口可便捷地访问这…

ssm+vue665基于Java的壁纸网站设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

Applied Artificial Intelligence

文章目录 一、征稿简介二、重要信息三、服务简述四、投稿须知五、联系咨询 一、征稿简介 二、重要信息 期刊官网&#xff1a;https://ais.cn/u/3eEJNv 三、服务简述 四、投稿须知 1.在线投稿&#xff1a;由艾思科蓝支持在线投稿&#xff0c;请将文章全文投稿至艾思科蓝投稿…

oracle-函数-NULLIF (expr1, expr2)的妙用

【语法】NULLIF (expr1, expr2) 【功能】expr1和expr2相等返回NULL&#xff0c;不相等返回expr1经典的使用场景&#xff1a; 1. 数据清洗与转换 在数据清洗过程中&#xff0c;NULLIF 函数可以用于将某些特定值&#xff08;通常是无效或不需要的值&#xff09;替换为 NULL&…

pycharm 安装

双击pycharm-community-2024.2.0.1.exe安装包 可以保持默认&#xff0c;也可以改成D&#xff0c;如果你有D 盘 全选&#xff0c;下一步 安装完成 在桌面创建一个文件夹任意名字 拖动到pycharm 图标打开 如果出现这个勾选信任即可 下面准备汉化&#xff08;喜欢英语界面的…

Matlab实现蚁群算法求解旅行商优化问题(TSP)(理论+例子+程序)

一、蚁群算法 蚁群算法由意大利学者Dorigo M等根据自然界蚂蚁觅食行为提岀。蚂蚁觅食行为表示大量蚂蚁组成的群体构成一个信息正反馈机制&#xff0c;在同一时间内路径越短蚂蚁分泌的信息就越多&#xff0c;蚂蚁选择该路径的概率就更大。 蚁群算法的思想来源于自然界蚂蚁觅食&a…

计算机毕业设计Hadoop+大模型高考推荐系统 高考分数线预测 知识图谱 高考数据分析可视化 高考大数据 大数据毕业设计 Hadoop 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 开题报告 题目&#xff1a…

【qwen2-1.5-instruct 好于Gemma2-2b-instruct\Llama3.2-1B-instruct】

最新的qwen Llama Gemma小参数模型比较&#xff0c;移动端 qwen2-1.5-instruct 好于Gemma2-2b-instruct\Llama3.2-1B-instruct 从 Qwen2–1.5B-instruct 到 Gemma2–2B-instruct&#xff0c;再到 Llama3.2–1B-instruct&#xff0c;最后是新的 Qwen2.5–1.5B-instruct。虽然我…

C++之位算法

位算法 常见位运算总结 位1的个数 给定一个正整数 n&#xff0c;编写一个函数&#xff0c;获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数&#xff08;也被称为汉明重量&#xff09;。 示例 1&#xff1a; 输入&#xff1a;n 11 输出&#xff1a;3 解释…

JAVA利用方法实现四道题

目录 1.给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返回它的索引 。如果不存在&#xff0c;则返回-1 2.计算字符串最后一个单词的长度&#xff0c;单词以空格隔开。&#xff08;注&#xff1a;字符串末尾不以空格为结尾&#xff09; 3.如果在将所…

【教程】Git 标准工作流

前言 Git 是日常开发中常用的版本控制工具&#xff0c;配合代码托管仓库&#xff08;如&#xff0c;Github&#xff0c;GitLab&#xff0c;Gitee 等&#xff09;用来实现多人多版本的协作开发。 但是 Git 的命令纷繁复杂&#xff0c;多如累卵&#xff0c;不可能也不需要全部搞…

基于AI深度学习的中医针灸实训室腹针穴位智能辅助定位系统开发

在中医针灸的传统治疗中&#xff0c;穴位取穴的精确度对于治疗效果至关重要。然而&#xff0c;传统的定位方法&#xff0c;如体表标志法、骨度折量法和指寸法&#xff0c;由于观察角度、个体差异&#xff08;如人体姿态和皮肤纹理&#xff09;以及环境因素的干扰&#xff0c;往…

金融标准体系

目录 基本原则 标准体系结构图 标准明细表 金融标准体系下载地址 基本原则 需求引领、顶层设计。 坚持目标导向、问题导向、结果 导向有机统一&#xff0c;构建支撑适用、体系完善、科学合理的金融 标准体系。 全面系统、重点突出。 以金融业运用有效、保护有力、 管理高…