【Linux】进程间的通信——学习使用手册

目录

前言

一、Linux 进程通信有哪些

二、管道(Pipe)

1.管道如何实现进程间的通信

2.管道特点

3.管道读取数据的四种的情况

4.示例程序

三.命名管道(Named Pipe或FIFO)

1.mkfifo函数

2.特点

3.示例程序

四.消息队列(Message Queue)

1.Message Queue如何工作?

2.创建消息队列

3.示例程序

5.共享内存和信号量(配合使用)

总结



前言

写这篇文章是想让初学者了解Linux进程间通信的整体框架 以及使用方法,还有就是此文章可以当成快速开发手册,直接复制修改其中的程序到自己的项目中。最近实习太忙了,文章的最后还有两个通信方式没有具体讲解。有时间补上

一、Linux 进程通信有哪些

在Linux系统中,进程之间可以通过多种方式进行通信,这些通信方式可以用于在不同进程之间传递信息、共享数据等。以下是一些常见的 Linux 进程间通信方式:

1.管道(Pipe)

描述: 管道是一种半双工的通信方式,它连接一个写进程和一个读进程,允许单向的数据流动。

创建: 使用 pipe 系统调用创建,可以通过 fork 创建的子进程间使用管道进行通信。

2.命名管道(Named Pipe或FIFO)

描述: 与管道类似,但它是一种有名字的管道,它允许在不相关的进程之间进行通信。

创建: 使用 mkfifo 命令或相关系统调用。

3.消息队列(Message Queue)

描述: 进程可以通过消息队列发送消息,消息队列是一个消息的链表,多个进程可以通过它进行通信。

创建: 使用 msgget、msgsnd 和 msgrcv 等系统调用。

4.信号(Signal)

描述: 进程可以通过信号向另一个进程发送异步通知,用于通知某个事件的发生。

使用: 使用 kill 命令或相关系统调用发送信号,接收信号可以通过注册信号处理函数。

5.共享内存(Shared Memory)

描述: 进程之间可以映射同一块物理内存,实现共享数据。

创建: 使用 shmget、shmat 和 shmdt 等系统调用。

6.信号量(Semaphore)

描述: 用于进程间的同步和互斥,可以用于保护共享资源。

创建: 使用 semget、semop 等系统调用。

7.套接字(Socket)

描述: 进程可以通过套接字在网络上或本地进行通信。

创建: 使用 socket、bind、connect 等系统调用。

8.文件锁(File Locking)

描述: 进程可以使用文件锁定机制来实现对文件的互斥访问。

创建: 使用 fcntl 等系统调用。

二、管道(Pipe)

1.管道如何实现进程间的通信

(1)父进程创建管道,得到两个件描述符指向管道的两端

(2)父进程fork出子进程,子进程也有两个文件描述符指向同管道。

(3)父进程关闭fd[1],子进程关闭fd[0],即子进程关闭管道读端,父进程关闭管道写端(因为管道             只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现             的,数据从写端流入从读端流出,这样就实现了进程间通信。

2.管道特点

(1)管道只允许具有血缘关系的进程间通信,如父子进程间的通信。

(2)管道只允许单向通信。

(2)管道内部保证同步机制,从而保证访问数据的一致性。

(4)面向字节流

(5)管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消              失。

(6)大小64K

3.管道读取数据的四种的情况

(1)读端不读(fd[0]未关闭),写端一直写

(2)写端不写(fd[1]未关闭),但是读端一直读

(3)读端一直读,且fd[0]保持打开,而写端写了一部分数据不写了,并且关闭fd[1]

(4)读端读了一部分数据,不读了且关闭fd[0],写端一直在写且f[1]还保持打开状态

4.示例程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 1024
int main() {
    int pipefd[2]; // 创建管道的文件描述符数组,0是读端,1是写端
    pid_t pid;
    char buffer[BUFFER_SIZE];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    pid = fork(); // 创建子进程
    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    if (pid > 0) { // 父进程
        close(pipefd[0]); // 关闭读端
        // 向管道写入数据
        char message[] = "Hello, child process!\n";
        write(pipefd[1], message, sizeof(message));
        close(pipefd[1]); // 关闭写端
        printf("Parent process completed.\n");
    } else { // 子进程
        close(pipefd[1]); // 关闭写端
        // 从管道读取数据
        int nbytes = read(pipefd[0], buffer, BUFFER_SIZE);
        printf("Child process received message: %s", buffer);
        close(pipefd[0]); // 关闭读端
        printf("Child process completed.\n");
    }
    return 0;
}

输出

Child process received message: Hello, child process!
Child process completed.
Parent process completed.

三.命名管道(Named Pipe或FIFO)

1.mkfifo函数

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

函数说明
    mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响

(1)当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。

(2)没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
返回值
    若成功则返回0,否则返回-1,错误原因存于errno中。

2.特点

(1)命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程。

(2)命名管道作为一种特殊的文件存放在文件系统中,而不是像管道那样存放在内核中。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会自行消失。

(3)和管道一样,命名管道也只能用于数据的单向传输,如果要用命名管道实现两个进程间数据的双向传输,建议使用两个单向的命名管道

3.示例程序

写入

#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
#define TEN_MEG (1024 * 1024 * 10)
int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];
    if(access(FIFO_NAME, F_OK) == -1)
    {
        res = mkfifo(FIFO_NAME, 0777);//读写执行
        if(res != 0)
        {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }
    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);
    if(pipe_fd != -1)
    {
        while(bytes_sent < TEN_MEG)
        {
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            if(res == -1)
            {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

运行结果

读取

#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_RDONLY;
    int bytes_read = 0;
    char buffer[BUFFER_SIZE + 1];
    memset(buffer, '\0', sizeof(buffer));
    printf("Process %d opening FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);
    if(pipe_fd != -1)
    {
        do
        {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes_read += res;
        } while (res > 0);
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
    exit(EXIT_SUCCESS);
}

运行结果

四.消息队列(Message Queue)

1.Message Queue如何工作?

用户消息缓冲区

无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下

struct msgbuf {
	long mtype;         /* 消息的类型 */
	char mtext[1];      /* 消息正文 */
};
  • 可通过mtype区分数据类型,同过判断mtype,是否为需要接收的数据
  • mtext[]为存放消息正文的数组,可以根据消息的大小定义该数组的长度

2.创建消息队列

通过msgget创建消息队列
函数原型如下

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

参数

key: 某个消息队列的名字

msgflg:由九个权限标志构成,用法和创建文件时使用的mode模式标志是一样的,这里举两个来说明

IPC_CREAT
	如果消息队列对象不存在,则创建之,否则则进行打开操作
IPC_EXCL
	如果消息对象不存在则创建之,否则产生一个错误并返回

返回值

那么如何获取key值?

通过宏定义key值
通过ftok函数生成key值,这里就不具体介绍ftok函数用法

添加信息到消息队列

向消息队列中添加数据,使用到的是msgsnd()函数
函数原型如下

int  msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

参数

  • msgid: 由msgget函数返回的消息队列标识码
  • msg_ptr:是一个指针,指针指向准备发送的消息,
  • msg_sz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
  • msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
    如:msgflg = IPC_NOWAIT 表示队列满不等待,返回EAGAIN错误

返回值

成功返回0

失败则返回-1

从消息队列中读取消息

从消息队列中读取消息,我们使用msgrcv()函数,
函数原型如下

int  msgrcv(int msgid, void *msg_ptr, size_t msgsz,
		 long int msgtype, int msgflg);

参数

  • msgid: 由msgget函数返回的消息队列标识码
  • msg_ptr:是一个指针,指针指向准备接收的消息,
  • msgsz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
  • msgtype:它可以实现接收优先级的简单形式
    msgtype=0返回队列第一条信息
    msgtype>0返回队列第一条类型等于msgtype的消息 
    msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息
  • msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
    msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
    msgflg=MSG_NOERROR,消息大小超过msgsz时被截断

注意

msgtype>0且msgflg=MSC_EXCEPT,接收类型不等于msgtype的第一条消息

返回值

  • 成功返回实际放到接收缓冲区里去的字符个数
  • 失败,则返回-1

消息队列的控制函数

函数原型

int  msgctl(int msqid, int command, strcut msqid_ds *buf);

参数

  • msqid: 由msgget函数返回的消息队列标识码
  • command:是将要采取的动作,(有三个可取值)分别如下

IPC_STAT
取出系统保存的消息队列的msqid_ds 数据,并将其存入参数buf 指向的msqid_ds 结构
中。
IPC_SET
设定消息队列的msqid_ds 数据中的msg_perm 成员。设定的值由buf 指向的msqid_ds结构给出。


IPC_RMID
将队列从系统内核中删除。


注意:若选择删除队列,第三个参数传NULL


返回值
如果操作成功,返回“0”;如果失败,则返回“-1”

3.示例程序

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

#define MSGKEY 123
//消息的数据结构是以一个长整型成员变量开始的结构体
struct msgstru
{
    long msgtype;
    char msgtext[2048];
};
int main()
{
    struct msgstru msgs;
    char str[256];
    int msg_type;
    int ret_value;
    int msqid;
    int pid;
    int status = 0;

    //检查消息队列是否存在
    msqid = msgget(MSGKEY, IPC_EXCL);//(键名,权限)
    if (msqid < 0)
    {
        //创建消息队列
        msqid = msgget(MSGKEY, IPC_CREAT | 0666);
        if (msqid <0)
        {
            printf("failed to create msq | errno=%d [%s]\n", errno, strerror(errno));
            exit(-1);
        }
    }
    pid = fork();//创建子进程
    if (pid > 0)
    {
        //父进程
        while (1)
        {
            printf("input message type:\n");//输入消息类型
            scanf("%d", &msg_type);
            if (msg_type == 0)
                break;
            printf("input message to be sent:\n");//输入消息信息
            scanf("%s", str);
            msgs.msgtype = msg_type;
            strcpy(msgs.msgtext, str);
            //发送消息队列(sizeof消息的长度,而不是整个结构体的长度)
            ret_value = msgsnd(msqid, &msgs, sizeof(msgs.msgtext), IPC_NOWAIT);
            if (ret_value < 0)
            {
                printf("msgsnd() write msg failed,errno=%d[%s]\n", errno, strerror(errno));
                exit(-1);
            }
            pid_t res = waitpid(pid, &status, WNOHANG);//回收子进程
            if (res > 0) {
                printf("wait child success!\n");
                printf("exit code: %d\n", WEXITSTATUS(status));
                break;
            }
        }
    }
    else if (pid == 0)
    {
        //子进程
        while (1)
        {
            msg_type = 1;//接收的消息类型为1
            msgs.msgtype = msg_type;
            //发送消息队列(sizeof消息的长度,而不是整个结构体的长度)
            ret_value = msgrcv(msqid, &msgs, sizeof(msgs.msgtext), msgs.msgtype, IPC_NOWAIT);
            if (ret_value > 0)
            {
                printf("read msg:%s\n", msgs.msgtext);
            }
        }
    }
    else
    {
        printf("fork error\n");
        //删除消息队列
        msgctl(msqid, IPC_RMID, 0);
        exit(1);
    }
    return 0;
}

运行结果

5.共享内存和信号量(配合使用)

示例程序

写端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

/*创建信号量
  初始化信号量
  操作信号量(P、V操作)
  删除信号量*/
/*创建共享内存
  连接共享内存
  读写共享内存
  断开共享内存
  删除共享内存*/
#define SHM_SIZE 1024  // 共享内存大小
// semctl函数控制信号量的相关信息
union semun{
    int         val;    /*信号量的值*/
    struct semid_ds *buf;   /*信号量集合信息*/
    unsigned short  *array;/*信号量值的数组*/
    struct seminfo  *__buf;/*信号量限制信息*/
};
// P上锁
void P(int semid) {
    struct sembuf set;
    set.sem_num = 0; //信号量在数组里的序号
    set.sem_op = -1; //信号量的操作值
    set.sem_flg = SEM_UNDO; //信号量的操作标识
    semop(semid, &set,1);
}
// V释放
void V(int semid) {
    struct sembuf set;
    set.sem_num = 0; //信号量在数组里的序号
    set.sem_op = 1; //信号量的操作值
    set.sem_flg = SEM_UNDO; //信号量的操作标识
    semop(semid, &set,1);
}
int main() {
    key_t key = ftok("/tmp", 1);  // 生成一个唯一的key
    // 创建共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    // 连接到共享内存
    char *shm_ptr = shmat(shmid, NULL, 0);
    if (shm_ptr == (char*)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    // 创建信号量
    int semid = semget(key, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }
    // 初始化信号量
    union semun seminit;
    seminit.val = 1;
    semctl(semid, 0, SETVAL, seminit);
    // 写入数据到共享内存
    P(semid);  // 上锁
    printf("Write content: ");
    fgets(shm_ptr, SHM_SIZE, stdin);
    shmdt(shm_ptr); // 分离共享内存
    V(semid);  // 解锁
   shmctl(shmid,IPC_RMID,0);  //删除共享内存段
   semctl(semid,0,IPC_RMID);   //删除信号量组
    return 0;
}

读端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

/*获取信号量(标识符)
  初始化信号量
  操作信号量(P、V操作)*/
/*获取共享内存(标识符)
  连接共享内存
  读写共享内存
  断开共享内存*/
#define SHM_SIZE 1024  // 共享内存大小
// P上锁
void P(int semid) {
    struct sembuf set;
    set.sem_num = 0; //信号量在数组里的序号
    set.sem_op = -1; //信号量的操作值
    set.sem_flg = SEM_UNDO; //信号量的操作标识
    semop(semid, &set,1);
}
// V释放
void V(int semid) {
    struct sembuf set;
    set.sem_num = 0; //信号量在数组里的序号
    set.sem_op = 1; //信号量的操作值
    set.sem_flg = SEM_UNDO; //信号量的操作标识
    semop(semid, &set,1);
}
int main() {
    key_t key = ftok("/tmp", 1);  // 生成一个唯一的key
    // 获取共享内存标识符
    int shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    // 连接到共享内存
    char *shm_ptr = shmat(shmid, NULL, 0);
    if (shm_ptr == (char*)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    // 获取信号量标识符
    int semid = semget(key, 1, 0666);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }
    // 读取共享内存中的数据
    P(semid);  // 进入临界区
    printf("Read content: %s", shm_ptr);
    shmdt(shm_ptr);// 分离共享内存
    V(semid);  // 离开临界区
    return 0;
}


总结

如有不足之处还请指正!

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值