Linux系统编程-进程间通信

一. 概念

1.1 发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

1.2 分类

1.2.1 管道

  • 匿名管道pipe
  • 命名管道

1.2.2 System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

1.2.3 POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

1.3 作用

  • 数据传输
  • 资源共享
  • 通知事件
  • 进程控制

二. 管道

2.1 简述

从一个进程连接到另一个进程的一个数据流称为一个“管道”。

2.2 匿名管道

2.2.1 pipe接口

int pipe(int pipefd[2]);
// 作用:创建一个匿名管道(以读写方式打开同一个文件)
// pipefd[0] --> 以读方式打开   pipefd[1] --> 以写方式打开
// 如果成功就返回0,错误就返回-1或者错误码

2.2.2 画图演示

在这里插入图片描述

2.2.2 代码演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>  // for pid_t
#include <sys/wait.h>

int main()
{
    int pipefd[2] = {0};
    int ret_pipe = pipe(pipefd);
    if (ret_pipe != 0)
    {
        perror("pipe");
        return -1;
    }
    
    // 我们让子进程写,父进程读
    pid_t pid = fork();  // 创建子进程
    if (pid < 0)
    {
        perror("fork");
        return -1;
    }
    if (pid == 0)
    {
        // child
        close(pipefd[0]);  // 子进程关闭读端,留下写端
        while (1)
        {
            const char* msg = "hello world";
            write(pipefd[1], msg, strlen(msg));
            sleep(1);
        }
    }
    
    // parent
    close(pipefd[1]);  // 父进程进程关闭写端,留下读端
    char msg[64] = "";
    while (1)
    {
        int n = read(pipefd[0], msg, sizeof(msg) - 1);
        msg[n] = '\0';
        printf("child say: %s\n", msg);
    }
    return 0;
}

2.2.3 关闭写端

// 只关闭写端
int main()
{
    int pipefd[2] = {0};
    int ret_pipe = pipe(pipefd);
    
    // 让子进程写,父进程读
    pid_t pid = fork();
    if (pid == 0)
    {
        // child
        close(pipefd[0]);
        while (1)
        {
            char ch = 'a';
            write(pipefd[1], &ch, 1);
            close(pipefd[1]);  // 将写端关闭
            break;
        }
    }
    
    // parent
    close(pipefd[1]);
    char msg[1024*4 + 1] = "";
    while (1)
    {
        sleep(1);
        int n = read(pipefd[0], msg, sizeof(msg) - 1);
        if (n < 0)
        {
            break;
        }
        if (n == 0)  // n为0,说明读端已经关闭了
        {
            printf("file is empty!\n");
            break;
        }
        msg[n] = '\0';
        printf("child say: %c\n", msg[0]);
    }
    return 0;
}

2.2.4 关闭读端

// 关闭读端
int main()
{
    int pipefd[2] = {0};
    int ret_pipe = pipe(pipefd);
    
    // 让子进程写,父进程读
    pid_t pid = fork();
    if (pid == 0)
    {
        // child
        close(pipefd[0]);
        while (1)
        {
            char ch = 'a';
            write(pipefd[1], &ch, 1);
        }
    }
    
    // parent
    close(pipefd[1]);
    char msg[1024*4 + 1] = "";
    while (1)
    {
        sleep(1);
        int n = read(pipefd[0], msg, sizeof(msg) - 1);
        close(pipefd[0]);  // 关闭读端
        printf("child say: %c\n", msg[0]);
        break;
    }
    int status = 0;
    wait(&status);
    printf("exit_code: %d   signal: %d\n", (status >> 8) & 0xff, status & 0x7f);  // 打印signal为13
    return 0;
}

2.2.5 五个特点

  • 管道是一个只能单向通信的通信信道
  • 管道是面向字节流的
  • 仅限于具有血缘关系的进程进行通信
  • 管道自带同步机制,原子性写入
  • 管道的生命周期是随进程的

2.2.6 读写规则

没有数据读时

写端不写或者写得慢,读端要等写端

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道满时

读端不读或者读得慢,写端要等读端

O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

写端对应的文件描述符被关闭时

读端read读完pipe的数据后会读到0,可以根据检测到0判断写端已经关闭了

读端对应的文件描述符被关闭时

写端收到SIGPIPE信号直接终止程序

原子性

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

2.3 命名管道

2.3.1 概念

一个有名字的管道文件,可以像正常文件操作那样被打开,所以它可以用于不相关进程之间的通信。

2.3.2 创建命令和接口

# 1. 命令行上创建
mkfifo filename
// 2. C程序里面创建
// 成功返回0,失败返回-1
int mkfifo(const char* filename, mode_t mode);

2.3.3 server和client案例代码

// server
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>  // for open
#include <fcntl.h>  // for open
int main()
{
    umask(0);
    int ret = mkfifo(FILE_NAME, 0666);
    int fd = open("fifo", O_RDONLY);
    while (1)
    {
        char buf[64] = {0};
        ssize_t n = read(fd, buf, sizeof(buf));
        printf("n -- %d\n", n);
        buf[n-1] = '\0';
        
        if (strcmp(buf, "ls") == 0)
        {
            if (fork() == 0)
            {
                execl("/bin/ls", "ls", "-al", NULL);
            }
        }
        else if (strcmp(buf, "information") == 0)
        {
            if (fork() == 0)
            {
                execl("/usr/bin/information", "information", NULL);
            }
        }
        else if (strcmp(buf, "clear") == 0)
        {
            if (fork() == 0)
            {
                execl("/usr/bin/clear", "clear", NULL);
            }
        }
        else if (strcmp(buf, "close") == 0)
        {
            printf("OK, close the file!!!\n");
            break;
        }
        else
        {
            printf("client say: %s\n", buf);
        }
    }
    close(fd);
    return 0;
}
// client
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>  // for open
#include <fcntl.h>  // for open
int main()
{
    int fd = open("fifo", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
    }
    printf("successful open, fd is %d\n", fd);
    while (1)
    {
        printf("请输入:");
        fflush(stdout);
        char buf[64] = {0};
        ssize_t n = read(0, buf, sizeof(buf));
        buf[n] = '\0';
        n = write(fd, buf, n);
        if (strcmp(buf, "close\n") == 0)
        {
            break;
        }
    }
    close(fd);
    return 0;
}

2.3.4 读写规则

打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

O_NONBLOCK enable:立刻返回成功

打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

2.4 匿名管道和命名管道的区别

  1. 匿名管道由pipe函数创建并且打开
  2. 命名管道由mkfifo命令或者函数创建,程序里面打开用open
  3. 匿名管道只能在有血缘关系的进程之间进行通信,而命名管道可以在不相关的进程之间通信

三. System V共享内存

3.1 简述

如下图,在物理内存上申请一块空间,然后让不同进程的虚拟内存通过页表映射挂接到这个这块空间,这块空间就能实现被不同进程共享了。共享内存在建立连接以后,在通信的时候不需要调用系统接口了,没有IO的处理,速度上是比管道快很多的。

在这里插入图片描述

3.2 函数接口

3.2.1 ftok

// 需要包含的头文件
#include <sys/types.h>  // for key_t
#include <sys/ipc.h>

// 函数声明
key_t ftok(const char* pathname, int proj_id);

// 一. 功能:一般是给像shmget这种接口生成一个key

// 二. pathname:自定义路径的名字

// 四. proj_id:自定义项目id

// 五. 返回值:成功就返回一个对应key,失败就返回-1

3.2.2 shmget

// 需要包含的头文件
#include <sys/ipc.h>
#include <sys/shm.h>

// 函数声明
int shmget(key_t key, size_t size, int shmflg);

// 一. 功能: 向物理内存申请共享内存

// 二. key:也就是ftok函数接口生成的

// 三. size:是共享内存的大小,建议写4KB(4096Byte)的整数倍,因为内存分配操作是以页为单位的,一页的大小就是4KB

// 四. shmflag:是一个位图映射的数据变量,主要的标志位有IPC_CREAT和IPC_EXCL
// IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作
// IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误(单独使用没有意义)

// 五. 返回值(成功返回共享内存的标识码,失败返回-1)
// 1. 单独使用IPC_CREAT,如果key对应的共享内存不存在,就返回新申请共享内存的标识码,存在就返回返回其标识码
// 2. IPC_CREAT和IPC_EXCL一起使用,如果key对应的共享内存不存在,就返回新申请共享内存的标识码,存在就返回出错(意义:调用成功,保证返回的标识码的共享内存是新建的,而不是已经存在的)

3.2.3 shmctl

// 需要包含的头文件
#include <sys/ipc.h>
#include <sys/shm.h>

// 函数声明
int shmctl(int shmid, int cmd, struct shmid_ds* buf);

// 一. 功能: 控制共享内存

// 二. shmid:需要控制的共享内存的标识码

// 三. cmd:将要采取的动作(IPC_STAT, IPC_SET, IPC_RMID)
// IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
// IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
// IPC_RMID:删除共享内存段(常用!!!)

// 四. buf:指向一个保存着共享内存的模式状态和访问权限的数据结构的指针(删除共享内存时可以传NULL)

// 五. 返回值:成功返回0,失败返回-1

3.2.4 shmat

// 需要包含的头文件
#include <sys/types.h>
#include <sys/shm.h>

// 函数声明
void* shmat(int shmid, const void* shmaddr, int shmflg);

// 一. 功能: 将进程的虚拟地址通过页表映射挂接到共享内存

// 二. shmid:需要控制的共享内存的标识码

// 三. shmaddr:指定虚拟地址,一般传NULL让系统自动找一块地址

// 四. shmflg:SHM_RDONLY,传这个过去为只读模式,否则为其他模式

// 五. 返回值:成功返回挂接上的虚拟地址,失败返回(void*)-1

3.2.5 shmdt

// 需要包含的头文件
#include <sys/types.h>
#include <sys/shm.h>

// 函数声明
int shmdt(const void* shmaddr)

// 一. 功能: 解除虚拟内存与共享内存的页面映射关系

// 三. shmaddr:需要去关联的虚拟地址

// 四. 返回值:成功返回0,失败返回-1

3.2.6 案例

// common.h文件
#include <stdio.h> 
#include <sys/types.h>  // for key_t
#include <sys/ipc.h> 
#include <sys/shm.h>   // for shmget 
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>  // for O_CREAT
#define PATH_NAME "./"
#define PROJ_ID 0x6666 
#define SIZE  4096
// server.c文件
#include "common.h" 
int main()
{   
    key_t key =  ftok(PATH_NAME, PROJ_ID);

    printf("key = %d\n", key);

    // 申请共享内存
    int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }

    printf("client success! shmid is %d\n", shmid);

   
    // 让虚拟内存和共享内存关联起来
    char* ptr = (char*)shmat(shmid, NULL, 0);
    //sleep(10);
    
    // 通信
    
    char ch = 'A';
    while(ch != 'Z')
    {
        *ptr = ch;
        ptr++;
        *ptr= '\0';
        ch++;
        sleep(1);
    }

    sleep(5);
    // 去关联
    shmdt(ptr);

    // 销毁共享内存
    int ret = shmctl(shmid, IPC_RMID, NULL);
    if (ret == -1)
    {
        perror("shmctl");
    }
    printf("Destruction of successful!\n");
    return 0;
}
// client.c文件
#include "common.h" 
int main()
{
    key_t key =  ftok(PATH_NAME, PROJ_ID);

    // 获取共享内存的标识码
    int shmid = shmget(key, SIZE, IPC_CREAT);
    if (shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    
    // 建立链接关系
    char* ptr = (char*)shmat(shmid, NULL, 0);
    
    // 通信
    int times = 26;
    while (times--)
    {
        sleep(1);
        printf("%s\n", ptr);
    }

    // 解除链接关系
    shmdt(ptr);

    
    return 0;
}

3.2.7 相关命令

# 1. 显示当前所有shmipc的信息
ipcs -m

# 2. 删除shmipc资源
ipcrm -m shmid  # shmid为共享内存的标识符  
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柿子__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值