Linux进程通信 系统调用总结

本文详细介绍了Linux下进程通信的八种方法,包括匿名管道、命名管道、内存映射、信号、共享内存、信号量、消息队列和socket。其中,匿名管道仅限于父子进程间通信,命名管道可以用于不相关进程间通信。内存映射通过mmap和munmap系统调用实现。信号是软中断,用于通知进程发生了异步事件。共享内存通过shmget、shmat、shmdt和shmctl等函数操作。信号量是用于多进程对共享数据对象的读取保护。消息队列则提供了一种消息传递机制,通过msgget、msgctl、msgsnd和msgrcv操作。最后,文章提到了socket常用于网络通信,以及ftok函数用于生成消息队列、信号量和共享内存的键值。
摘要由CSDN通过智能技术生成

1. 进程通信概述

Linux下进程通信的八种方法:

  • 匿名管道(pipe),

  • 命名管道(FIFO),

  • 内存映射(mapped memeory),

  • 消息队列(message queue),

  • 共享内存(shared memory),

  • 信号量(semaphore),

  • 信号(signal)

  • 套接字(Socket)

2. 匿名管道(pipe)

匿名管道用于进程之间通信,且仅限于本地父子进程之间通信,结构简单

  • 只提供单向通信,也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西,那么进程2 就只能读取文件的内容。
  • 只能用于具有血缘关系的进程间通信,通常用于父子进程建通信
  • 管道是基于字节流来通信的
  • 依赖于文件系统,它的生命周期随进程的结束结束(随进程)
  • 其本身自带同步互斥效果

在LINUX编程中匿名管道通常使用pipe系统调用完成。shell命令行中**管道符|**的本质也是匿名管道

2.1 pipe

用法:

int pd[2];
int r = pipe(pd);

pipe()函数在内核中创建一个管道并在传入参数pd[2]中返回两个文件描述符,其中pd[0]用于从管道读取,pd[1]用于从管道写入数据。如果成功返回0,失败返回-1。

需要注意,管道是多进程间通信的概念,因此在一个进程中通过pipe系统调用创建一个管道后并不能直接从中读取数据,会永远等待下去。因为创建管道后该管道中没有数据,此时读取端进程读数据时需要先等待写入端进程写入数据后才能读。然而这里写进程和读进程都是同一个进程,因此会永远等待。因此进程只能是管道的一个读进程或者写进程之一,而不能二者都是。

因此正确方式是创建一个管道后fork复刻一个子进程来共享该管道。由于fork的子进程会继承父进程的所有打开文件描述符,因此子进程也有pd[0](读取端)和pd[1](写入端)。然后将父进程和子进程分别指定为该管道的读取端或者写入端(即为将一个进程指定为管道的读取端,另一个进程指定为该管道的写入端),且父子进程都要关闭掉他不需要的文件描述符。例如父进程指定为写进程,子进程指定为读进程。那么父进程关闭掉pd[0],子进程关闭掉pd[1]。之后就可以父进程向管道写数据,子进程从该管道读取数据。

示例:父进程创建管道并fork子进程,指定父进程为管道写入端、子进程为读取端,进程父子进程间单向通信

main.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
   
    int pd[2];
    int r = pipe(pd);//创建管道
    printf("pipe: read: %d write: %d\n",pd[0],pd[1]);
    int pid = fork();//fork子进程
    if(pid){
   
        //父进程执行此部分
        close(pd[0]);   //设置父进程为管道的写入端,因此关闭读取端文件描述符pd[0]
        char buf[] = "this is some datas";
        sleep(3);   //父进程sleep三秒
        printf("parent Process begin write datas\n");
        write(pd[1],buf,strlen(buf));   //向管道写入数据
        printf("parent Process write datas done\n");
    }
    else{
   
        //子进程执行此部分
        close(pd[1]);   //设置子进程为管道的读取端,因此关闭写入端文件描述符pd[1]
        char buf[30];
        int num = read(pd[0],buf,sizeof(buf)-1);    //从管道读取数据
        buf[num] = '\0';	//字符串结尾
        printf("son Process get %d datas : %s\n",num,buf);
    }
}

运行结果

xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ gcc pipe_test.c 
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ ./a.out 
pipe: read: 3 write: 4
parent Process begin write datas
parent Process write datas done
son Process get 18 datas : this is some datas

可见父进程成功将数据写入管道,子进程成功从管道读出数据。由于父进程在写入前sleep了三秒,可以证实管道读取端要读数据时,如果管道为空且存在写进程时会等待写入端写入数据后再读取。

2.2 管道命令

管道符号:|用法:

cmd1 | cmd2

cmd1的输出编程cmd2的输入,这是通过管道实现的。sh通过一个进程运行cmd1,一个进程运行cmd2,它们之间通过一个管道连在一起完成进程间通信。

其具体实现逻辑大致如下:

  • sh进程获取命令行cmd1 | cmd2,复刻出一个子进程sh并等待子进程sh终止

  • 该子进程sh执行以下代码:需要注意exec更改进程执行映像并不会关闭原先进程已经打开的文件描述符,因此exec之前打开的文件描述符在更改执行映像后还能继续使用(除非该文件描述符是FD_CLOEXEC)。

    close-on-exec标志(FD_CLOEXEC):内核为每个文件描述符提供了执行时关闭标志,当exec()执行成功之后,会自动关闭设置了FD_CLOEXEC标志的文件描述符,如果exec()调用失败,文件描述符依然会保持打开状态

    int pd[2];
    pipe(pd);//创建管道
    int pid = fork();//复刻一个子进程
    if(pid){
         
        //父进程执行此部分
        
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值