Linux【7】------Linux系统编程(进程间通信IPC)

文章目录

  • [1 信号](#1 信号)
  • [2 消息队列](#2 消息队列)
      • [2.1 概念](#2.1 概念)
      • [2.2 创建消息队列](#2.2 创建消息队列)
      • [2.3 访问消息队列](#2.3 访问消息队列)
      • [2.4 控制消息队列](#2.4 控制消息队列)
  • [3 共享内存](#3 共享内存)

1 信号

信号是一种异步通信方式

同步通信

同步指的是当进程发起一个请求,但是该请求并未马上响应,则进程就会阻塞等待,直到请求被响应
异步通信

异步指的是当进程发起一个请求,如果该请求并未马上响应,则进程会继续执行其他的任务,过来一段时间请求得到了响应,则会通知该进程,该进程得到通知再去对请求做出处理。

一个信号发给一个进程时,操作系统会中断收到信号的进程(如果此进程此时执行的是非原子性操作)。

1.1 信号类型

1~ 31的信号为普通信号,编号为34~64的信号为实时信号

  • 普通信号
    Linux系统中的普通信号也被称为不可靠信号,指的是当进程接收到了很多的信号请求但是又不能及时处理时,不会把信号形成队列,而是把其余未被处理的信号直接丢弃,只留下一个信号。
  • 实时信号
    Linux系统中的实时信号也被称为可靠信号,指的是当进程接收到了很多信号请求但是又不能及时处理时,会把未处理的信号形成队列 ,然后按照顺序依次处理,不会丢弃信号。Linux系统中的实时信号是新增加的。

1.2 信号含义


1.3 信号产生

按键

以ctrl+c为例

ctrl+c 后,键盘产生一个硬件中断

操作系统会解释为sigint信号,然后记录在PCB

此时CPU暂停用户空间代码,执行内核空间的硬件中断。硬件中断执行完后CPU返回用户空间,cpu发现进程pcb中的信号为sigint,则会把进程切换到终止态。

硬件异常

由系统硬件检测

调用接口

kill() 可向指定进程发信号
raise() 只能向当前进程发信号

发送指令

可以在shell里直接kill

内核检测

根据软件条件产生信号

例如 sigalrm

1.4 信号处理

默认
捕捉

进程接收到某个指定信号之前,先设计好该信号响应函数,并把该信号和该响应接口进行关联,这样当进程接收到信号之后,就不会执行信号的默认响应动作,而是执行用户指定的响应动作。

c 复制代码
sighandler_t signal(int signum, sighandler_t handler);

signum 是目标信号编号

handler 是信号处理函数的地址

sighandler_t 为void (*sighandler_t)(int)函数指针

当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。
练习:用户设计两个程序,要求进程A中自定义信号SIGUSR1的响应接口,要求进程B每隔一段时间向进程A就发送SIGUSR1信号,测试进程A是否可以执行关联的响应接口。

c 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>

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即可。

1.5 信号阻塞

sigprocmask()接口设置信号集的属性

用户需要创建信号集,并添加相关信号到信号集

阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

练习:根据阻塞接口,设计一个程序,要求把快捷键Ctrl+C的对应信号进行阻塞,需要创建一个信号集,把该信号添加到信号集,对信号集属性进行设置(阻塞),然后测试发送该信号是否会被进程响应。

c 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>


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

1.6 信号挂起

就是当进程没被调度运行时,发送来的信号会被放入一个信号集,里面存储了待处理的信号,当进程再次被调度时,这些信号才会被处理。

2 消息队列

2.1 概念

消息队列(Message Queue)是一种用于在分布式系统中实现异步通信的中间件技术。它允许生产者(发送方)将消息发送到队列,消费者(接收方)从队列中异步获取并处理消息。

一个消息队列只能存在一种数据类型

操作系统为每个消息队列分配唯一键值,进程可以用键值指定消息队列来发消息。

2.2 创建消息队列

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()函数获取文件的属性信息。

c 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/msg.h>
#include <sys/ipc.h>

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

2.3 访问消息队列

msgsnd函数,用于向指定消息队列发送消息,
msgrcv函数,利用该接口从指定消息队列读消息

发消息

c 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • msqid 队列标识符,由msgget函数返回得到
  • msgp 指向struct msgbuf 类型结构体的指针
c 复制代码
struct msgbuf{
	long mtype;    //消息类型
	char mtext[1]; //消息正文
}
  • msgsz 正文大小,按字节计算
  • msgflg 指消息队列标志,默认阻塞(当前写入的长度大于消息队列剩余的空间时会等待消息队列空间足够时)

读取消息

c 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
  • msgqid 指的是MSG对象的标识符ID,MSG标识符可以通过msgget()函数获取。
  • msgp 指的是存放消息的缓存地址,该地址下存储的是struct msgbuf结构体。
  • msgsz 指的是存放消息的缓存的大小,按照字节计算,如果消息正文的大小大于用户设置的缓存大小,则根据msgflg是否为MSG_NOERROR进行判断,如果msgflg设置为MSG_NOERROR ,则可以读取对应字节的消息,如果msgflg没有设置,则无法读取消息并报错。
  • msgtyp 指的是要接收消息的类型,在调用msgsnd函数时构造的消息结构体中有该成员的值。
    • 等于0:指的是不区分类型,直接读取MSG中的第一个消息。
    • 大于0:读取类型为指定msgtyp的第一个消息(若msgflg被配置了MSG_EXCEPT则读取除了类型为msgtyp的第一个消息)。
    • 小于0:读取类型小于等于msgtyp绝对值的第一个具有最小类型的消息。例如当MSG对象中有类型为3、1、5类型消息若干条,当msgtyp为-3时,类型为3的第一个消息将被读取。
  • msgflg 指的是接收消息选项,如果msgflg设置为0,指的是默认接收模式,在MSG中无指定类型消息时阻塞。
    *
    1. IPC_NOWAIT :指的是非阻塞接收模式,当MSG中没有指定类型消息时直接退出函数
      1. MSG_EXCEPT :指的是读取除msgtyp之外的第一个消息。
      1. MSG_NOERROR:如果待读取的消息尺寸比msgsz大只返回msgsz部分,其余部分丢弃

练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容。

c 复制代码
/*练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容。*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

#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;
}

2.4 控制消息队列

IPC对象是需要手动删除的

  • 指令
    • ipcrm {shm|msg|sem} id
  • 函数
    • int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    • 一般buf可为空
c 复制代码
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) */
           };

3 共享内存

未完待续...

相关推荐
MonkeyBananas6 分钟前
在Ubuntu中使用Apache2部署项目
linux·运维·ubuntu
潇-xiao10 分钟前
vim的相关命令 + 三种模式(10)
linux·编辑器·vim
Wukong.Sun16 分钟前
操作系统的概念,功能和目标
java·linux·开发语言·windows
茶本无香23 分钟前
Linux Alias 魔法:命令行效率提升秘籍
linux
万象.23 分钟前
进程地址空间
linux·centos
什么半岛铁盒34 分钟前
C++信号处理程序解析与改进
linux·c++·信号处理
孙克旭_1 小时前
day033-备份服务rsync
linux·运维·rsync
sztomarch2 小时前
Router-Routing
linux·运维·服务器·前端·网络
achene_ql3 小时前
手写muduo网络库(七):深入剖析 Acceptor 类
linux·服务器·开发语言·网络·c++
凉、介3 小时前
Linux 下 pcie 初始化设备枚举流程代码分析
linux·运维·服务器·学习·嵌入式·c·pcie