栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Linux系统编程-进程间通信(用时一周总结,非常详细,建议收藏)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 
#include 
#include 
#include 
#include   // for pid_t
#include 

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] = '';
        printf("child say: %sn", 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] = '';
        printf("child say: %cn", 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: %cn", msg[0]);
        break;
    }
    int status = 0;
    wait(&status);
    printf("exit_code: %d   signal: %dn", (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 
#include 
#include 
#include 
#include   // for open
#include   // 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 -- %dn", n);
        buf[n-1] = '';
        
        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: %sn", buf);
        }
    }
    close(fd);
    return 0;
}
// client
#include 
#include 
#include 
#include 
#include   // for open
#include   // for open
int main()
{
    int fd = open("fifo", O_WRONLY);
    if (fd < 0)
    {
        perror("open");
    }
    printf("successful open, fd is %dn", fd);
    while (1)
    {
        printf("请输入:");
        fflush(stdout);
        char buf[64] = {0};
        ssize_t n = read(0, buf, sizeof(buf));
        buf[n] = '';
        n = write(fd, buf, n);
        if (strcmp(buf, "closen") == 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   // for key_t
#include 

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

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

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

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

// 五. 返回值:成功就返回一个对应key,失败就返回-1
3.2.2 shmget
// 需要包含的头文件
#include 
#include 

// 函数声明
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 
#include 

// 函数声明
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 
#include 

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

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

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

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

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

// 五. 返回值:成功返回挂接上的虚拟地址,失败返回(void*)-1
3.2.5 shmdt
// 需要包含的头文件
#include 
#include 

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

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

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

// 四. 返回值:成功返回0,失败返回-1
3.2.6 案例
// common.h文件
#include  
#include   // for key_t
#include  
#include    // for shmget 
#include 
#include 
#include   // 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 = %dn", key);

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

    printf("client success! shmid is %dn", shmid);

   
    // 让虚拟内存和共享内存关联起来
    char* ptr = (char*)shmat(shmid, NULL, 0);
    //sleep(10);
    
    // 通信
    
    char ch = 'A';
    while(ch != 'Z')
    {
        *ptr = ch;
        ptr++;
        *ptr= '';
        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("%sn", ptr);
    }

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

    
    return 0;
}
3.2.7 相关命令
# 1. 显示当前所有shmipc的信息
ipcs -m

# 2. 删除shmipc资源
ipcrm -m shmid  # shmid为共享内存的标识符  
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1005691.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号