Linux系统编程——进程之间通信 无名管道&有名管道

文章目录

    • 什么叫进程通信
    • 进程案例代码
    • 进程使用用户空间缓存通信方式
    • 进程与进程之间通信方式:
      • 无名管道
        • 案例——一个进程对管道进行写读。
        • 案例一个进程操作管道——管道无内容,在读进入阻塞
        • 案例一个进程操作管道——管道写满在了,在写数据。(写满管道时在写就会阻塞)
        • A进程与B进程通信
      • 有名管道
        • 有名管道实现无亲缘线程通信

进程通信:在用户空间实现进程通信是不可能的,通过Linux内核通信
线程通信:可以在用户空间就可以实现,可以通过全局变量通信。

什么叫进程通信

进程通信是指在进程间传输数据(交换信息)。 进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。

进程案例代码

使用**fork()**函数创建的进程具有亲缘关系。
fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

#include 
#include 
#include 

int main(){
    pid_t pid;
    pid = fork();
    if (pid == 0)   //子进程
    {
        int i = 0;
        for(i=0;i<5;i++){
            printf("this is child process i=%d\n",i);
            usleep(100);
        }
    }

    if (pid>0)      //父进程
    {
        int i = 0;
        for(i=0;i<5;i++){
            printf("this is parent process i=%d\n",i);
            usleep(100);
        }
    }
    return 0;
}

运行结果:
Linux系统编程——进程之间通信 无名管道&有名管道_第1张图片

进程使用用户空间缓存通信方式

在用户空间定义一个变量,用来控制两个进程的运行。运行结果只有父进程成功打印了信息。

#include 
#include 
#include 

int main(){
    pid_t pid;

    pid = fork();

    int thread_entry = 1;

    if (pid == 0)   //子进程
    {
        int i = 0;
        while(thread_entry==1);
        for(i=0;i<5;i++){
            printf("this is child process i=%d\n",i);
            usleep(100);
        }
    }

    if (pid>0)      //父进程
    {
        int i = 0;

        for(i=0;i<5;i++){
        printf("this is parent process i=%d\n",i);
        usleep(100);
        }
        thread_entry=0;
    }
    
    return 0;
}

运行结果
Linux系统编程——进程之间通信 无名管道&有名管道_第2张图片

对于该案例:进程与进程之间是独立的空间。在用户空间实现进程与进程之间的通信是不可能的。
进程与进程之间的通信方式:可以使用内核空间对象进行通信。

进程与进程之间通信方式:

  • 管道通信:无名管道(文件系统中无文件名),有名管道(文件系统中有名)

  • 信号通信:信号(通知)通信包括:信号的发生,信号的接收和信号的处理

  • IPC(Inter-Process Communication)通信:共享内存、消息队列和信号灯

    以上是单机通信,只有一个Linux内核

  • Socket通信:存在于一个网络中两个进程之间的通信(两个Linux内核)

无名管道

无名管道是Linux系统内核的特殊文件,用于进程之间的通信。无名管道相当于一个队列结构,fd[1]为写入端(入队),fd[0]为读出端(出队)。其中信息读出后即删除,再次读取时即为下一个信息。管道是半双工的,数据只能向一个方向流动。只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。

无名管道缺陷:
不能实现不是父子线程(亲缘关系)之间的通信

fork()函数创建的进程有一个好处,子进程会对父进程的所有内容进行拷贝,包括通过pipe函数创建的文件描述符,所以fork()函数创建的进程可以对内核空间多的同一个管道进行操作。

通信原理:
Linux系统编程——进程之间通信 无名管道&有名管道_第3张图片
管道问价是一个特殊的文件,由队列实现的。
在文件IO中创建一个文件或打开一个文件是由open函数来实现的,他不能创建管道文件。只能用pipe函数来创建管道

//创建管道为系统调用 unistd.h
参数:就是得到文件描述符。可见有两个文件描述符:
fd[0]和fd[1]。管道有一个读端fd[0]用来读,和一个写端fd[1]用来写,
这个规则不能改变。

返回值:成功是0,错误:-1
int pipe(int fd[2])
  • 注意
    1.管道是创建在内存中的,进程结束,空间就释放了,管道就不存在了。
    2.管道中的东西,读完了就删除了。
    2.如果管道中没有任何东西可读,就会阻塞了。
案例——一个进程对管道进行写读。
#include 
#include 
#include 

int main(){
    int fd[2];
    int ret;

    char write_buf[] = "hello world";
    char read_buf[128] = {0};
    ret = pipe(fd);

    if (ret<0)
    {
        printf("create pipe failure\n");

        return -1;
    }

    printf("create pipe successs fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);
    
    write(fd[1],write_buf,sizeof(write_buf));

    read(fd[0],read_buf,128);
    printf("%s\n",read_buf);

    close(fd[1]);
    close(fd[0]);

    return 0;
}

运行结果:

从上述可以看出,一个进程是一个对无名管道进行写读的。

案例一个进程操作管道——管道无内容,在读进入阻塞
#include 
#include 
#include 

int main(){
    int fd[2];
    int ret;

    char write_buf[] = "hello world";
    char read_buf[128] = {0};
    ret = pipe(fd);

    if (ret<0)
    {
        printf("create pipe failure\n");

        return -1;
    }

    printf("create pipe successs fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);
    
    write(fd[1],write_buf,sizeof(write_buf));	//往管道写数据

    read(fd[0],read_buf,128);				//第一次读管道数据
    printf("%s\n",read_buf);
    memset(read_buf,0,128);

    read(fd[0],read_buf,128);			//第二次读管道数据
    printf("%s",read_buf);

    close(fd[1]);
    close(fd[0]);

    return 0;
}

运行结果:

查看进程状态:

S+表示进程进入了睡眠状态。

可以看出第一次读写成功了。但是读完第一次之后,管道内容就会删除,此时在读就会进入阻塞状态。

证明了当管道为空时,在进行读操作就会进入阻塞状态

案例一个进程操作管道——管道写满在了,在写数据。(写满管道时在写就会阻塞)
#include 
#include 

int main(){
    int fd[2];

    int ret;

    int i;

    char write_buf[] = "hello world";

    char read_buf[128] = {0};

    ret = pipe(fd);

    if(ret < 0 ){
        printf("create pipe failure\n");
        return -1;
    }

    printf("printf pipe success fd[0] = %d, fd[1] = %d\n",fd[0],fd[1]);

    while(i<5500){
        write(fd[1],write_buf,sizeof(write_buf));
        i++;
    }

    //read(fd[0],read_buf,128);

    printf("write end\n");

    close(fd[1]);
    close(fd[0]);

}

运行结果:

此时并没有打印出 “write end” 语句。

查看进程状态:

S+表示睡眠状态。此时./a,out进入了睡眠状态。说明了当管道写满了时,在写就会进入阻塞状态。

通过这个程序我们也可以计算出无名管道的缓存有多大。

在程序中循环次数为5500次。我们 不断减小次数,查看运行结果。

i<5457时

while(i<5457){
        write(fd[1],write_buf,sizeof(write_buf));
        i++;
    }

运行结果:

i<5456时

while(i<5456){
        write(fd[1],write_buf,sizeof(write_buf));
        i++;
    }


由此可以看出。管道缓存大小是 hello world X 5456那么大。如果超过这个范围,再次写管道就会进入到阻塞状态。

A进程与B进程通信
#include 
#include 
#include 

int main(){
    pid_t pid;

    int fd[2];
    int ret;
    
    char thread_entry = 0;

    ret = pipe(fd);
    
    if(ret < 0 ){
        printf("cretae pipe failure\n");

        return -1;
    }

    printf("create pipe success\n");

    pid = fork();

    if (pid == 0)   //子进程
    {
        int i = 0;
        read(fd[0],&thread_entry,1);

        while(thread_entry == 0);
        for(i=0;i<5;i++){
            printf("this is child process i=%d\n",i);
            usleep(100);
        }
    }

    if (pid>0)      //父进程
    {
        int i = 0;

        for(i=0;i<5;i++){
        printf("this is parent process i=%d\n",i);
        usleep(100);
        }
        thread_entry = 1;
        write(fd[1],&thread_entry,1);
    }
    while(1);
    return 0;
}

分析://父进程先运行,运行完毕之后往管道里写一个值,thread_entry = 1;然后子进程允许之前先读管道。如果管道是空的,那么程序就会停下这里,因为管道有读阻塞。父进程执行完毕会写管道,那么子进程就会从睡眠状态转换的运行状态,就会运行后面的代码。

有名管道

有名管道也叫命名管道,在文件系统目录中存在一个管道文件。
管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。

有名管道具有以下特点:
①它可以使互不相关的两个进程间实现彼此通信;
②该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便;
③FIFO严格地遵循先进先出规则,对管道及FIFO的读操作总是从开始处返回数据,对它们的写操作则是把数据添加到末尾。

mkfifo:用来创建管道文件的节点,没有在内核中创建管道,只有通过open函数打开这个文件时才会在内核空间创建管道。

int mkfifo(const char* fileName,mode_t mode)
头文件:
sys/types.h
sys/stat.h
创建管道文件
参数:
管道文件名,权限,创建的文件权限任然和umask有关系。
返回值:创建成功返回0,创建失败返回-1

管道文件,字符设备文件,块设备文件,套接字文件。这几种文件类型不占空间,只有文件节点。

有名管道实现无亲缘线程通信

进程A的代码

#include 
#include 
#include 

int main(){
    int i;

    int fd;

    char process_inter = 0;

    fd = open("./myfifo",O_WRONLY);

    if(fd < 0){
        printf("open myfifo failure\n");
        return -1;
    }

    printf("open myfifo success\n");

    for(i=0;i<5;i++){
        printf("this is first process i=%d\n",i);
        usleep(100);
    }

    process_inter = 1;
    usleep(500);
    write(fd,&process_inter,1);
    while(1);
    return 0;
}

进程B的代码:

#include 
#include 
#include 
#include 

int main(){
    int i;

    int fd;

    char process_inter = 0;

    fd = open("./myfifo",O_RDONLY);

    if(fd < 0){
        printf("open myfifo failure\n");
        return -1;
    }

    printf("opem myfifo success\n");

    read(fd,&process_inter,1);
    while(process_inter==0);

    for(i=0;i<5;i++){
        printf("this is first process i=%d\n",i);
        usleep(100);
    }


    while(1);
    return 0;
}

将两个.c文件编译之后。为了验证实验。我们先自行进程B
Linux系统编程——进程之间通信 无名管道&有名管道_第4张图片
可以发现second进程B一直等待执行。那么我们在另外一个中断执行进程A
Linux系统编程——进程之间通信 无名管道&有名管道_第5张图片
当进程A执行完毕,进程B在执行。

你可能感兴趣的:(Linux,linux,服务器)