Linux进程通信——有名管道

管道(pipe)是无名管道,他是进程资源的一部分,随着进程的结束而消失。并且它只能在拥有公共祖先进程的进程内通信。而有名管道(FIFO)的出现则解决了这个问题。FIFO提供了一个路径名与它关联。这样可以通过访问该路径就能使得两个进程之间相互通信。此处的FIFO严格遵守“先进先出”原则。读总是从头开始的,写总是从尾部进行的。匿名管道和FIFO都不支持lseek函数对他们操作。Linux下建立有名管道的函数是mkfifo。

函数原型: int mkfifo(const char * pathname,mode_t mode);

函数功能:创建一个FIFO文件,用于进程之间的通信。pathname就是路径名,mode是该文件的权限。返回值表示是否成功,返回0表示成功,返回-1表示失败,失败原因在errno中。(建立FIFO的时候,要求不存在这样的FIFO)。

和pipe创建匿名管道相比,mkfifo需要设置文件权限。这是因为pipe创建的匿名管道是内核借助缓冲区创建的,它不在磁盘上真实存在,只在内存中存在。而mkfifo创建的有名管道是在磁盘上真实存在的,因此需要文件权限,正是由于真实存在这样一个文件,所以才克服了在任意进程间通信的问题。

例如执行下面代码来创建一个FIFO。

#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>

int main()
{
    int ret;
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    return 0;
}

 可以看到,它以P开头,表示它是一个FIFO文件。它是真实存在于磁盘上的,不仅仅在内存中。进程结束了这个文件仍然在。FIFO和匿名管道一样,默认下要考虑阻塞。

  1. 当使用O_NONBLOCK标志的时候,打开FIFO文件,读取操作会立即返回。但是如果没有进程读取FIFO文件,那么写入FIFO的操作会返回ENXIO错误代码。
  2. 不使用O_NONBLOCK标志时,打开FIFO的操作必须等待其他进程写入FIFO后才执行,当然写入FIFO的操作也必须等到其他进程来读取FIFO以后才能执行。

当存在这个FIFO文件的时候,再次创建这个FIFO会显示File exists。

首先,第一种情形的测试。

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


int main()
{
    int ret;
    int fd;
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_NONBLOCK|O_WRONLY);        //只写打开文件
        if(0 > fd)
        {
            perror("open FIFO");
        }
        else
        {
            write(fd,"Hello",5);
            printf("Write Over\n");
        }
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);       //在设置O_NONBLOCK标志的情形下,让父进程先写入
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

运行结果如下:

可以看到会发生错误,因为它没有阻塞。那么接着试一下直接读一个FIFO文件,看看会发生什么。

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


int main()
{
    int ret;
    int fd;
    char str[30] = {0};
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_NONBLOCK|O_RDONLY);   //注意这里和上面不一样,改成O_RDONLY
        if(0 > fd)
        {
            perror("open FIFO");
        }
        else
        {
            read(fd,str,sizeof(str));
            printf("Read Over\n");
        }
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);       //在设置O_NONBLOCK标志的情形下,让父进程直接读取
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

 看到的结果是对一个空的FIFO文件打开并执行read操作是没有问题的。

先以只读方式打开,如果没有进程已经为写而打开一个 FIFO, 只读 open() 成功,并且 open() 不阻塞。

下面,当不设置O_NONBLOCK标志的时候,FIFO和匿名管道的处理方式是一样的。管道这个名字是非常形象的,一个管道必须有两端(就是在一个进程中必须读,另一个进程必须写),只有这样,才能正常操作,否则进程将会阻塞。例如下面这样。

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

int main()
{
    int ret;
    int fd;
    char str[30] = {0};
    ret = mkfifo("My_FIFO",0666);
    if(0 != ret)
    {
        perror("mkfifo");
    }
    pid_t pid;
    pid = fork();
    if(0 < pid)
    {
        fd = open("My_FIFO",O_WRONLY);
        if(0 > fd)
        {
            perror("Write FIFO");
        }
        else
        {
            write(fd,"Hello",5);
            printf("Write Over\n");
        }
        wait(NULL);
        exit(0);
    }
    if(0 == pid)
    {
        sleep(2);
        // fd = open("My_FIFO",O_RDONLY);
        // read(fd,str,5);
        exit(0);
    }
    if(-1 == pid)
    {
        perror("fork");
    }
    return 0;
}

我们仅仅在父进程中进行了写(write),没有其他进程在读(read)。这样造成的结果是进程一直阻塞在这里,如下

没有输出结果,阻塞在这里不动了。而当我们加上注释掉了那两句话以后,程序就会有输出,结果如下:

或者说,这也体现了进程的并发行,管子有了一端以后,还必须有另一端,这才能构成管道。

上述的情形都是在只读或者只写下打开FIFO文件而言的,如果使用O_RDWR打开FIFO文件,那么除了read空FIFO会阻塞。其余将不会有问题。

测试一下,FIFO用于两个无关进程直接的通信。首先建立我们有两个进程,一个是test1,另一个是test2.

//test1的源代码
//test1是写FIFO

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

int main()
{
    int fd,ret;
    char str[10] = {"Hello"};
    ret = mkfifo("fifo",0666);      //test1.c中创建FIFO文件
    fd = open("fifo",O_WRONLY);     //只写方式打开
    write(fd,str,5);
    close(fd);

    return 0;
}
//test2的源代码
//test2是读FIFO

#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    int fd;
    char str[10] = {0};
    fd = open("fifo",O_RDONLY);
    read(fd,str,5);
    printf("%s\n",str);
    close(fd);
    return 0;
}

我们把test1和test2的源代码生成可执行文件后,打开两个终端。如果我先运行test1,然后运行test2.那么test2将读取到FIFO中的数据。如下所示。

我们没有设置O_NONBLOCK,先运行test1之后,会发现test1阻塞在这里。等我们把test2也运行了之后,test1不在阻塞,运行结束,然后test2也成功打印出了Hello。

换个运行顺序,我们先运行test2,然后运行test1.这样会发现test2阻塞在这里。等我们把test1也运行了之后,test2不在阻塞,向屏幕打印Hello.

如果我们不想让FIFO阻塞,那么打开文件的时候设置为可读可写即可

fd = open("fifo", O_RDWR);

当然,如果FIFO是空的,那么即使设置了可读可写,read()操作仍旧会阻塞。这样的运行结果和上面所说的是一致的。自己运行一下才能深刻理解。这里不好用图片说明。

调用 write() 函数向 FIFO 里写数据,当缓冲区已满时 write() 也会阻塞。

FIFO的命令行用途

首先使用mkfifo命令创建一个FIFO文件,然后将ls的输出重定向到myfifo。

可以看到阻塞在了这里,我们现在打开另外一个终端,输入cat myfifo。

这样就将ls的结果打印出来了,同时另外一个终端也不再阻塞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信号量是一种用于进程间通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号量来实现进程间的同步和互斥。以下是信号量的基本概念: - 计数器:信号量的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号量的值减1。如果信号量的值为0,则进程将被阻塞,直到信号量的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号量的值加1。如果有进程正在等待该信号量,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号量来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号量集合,并使用semctl函数对信号量进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号量的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号量集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号量集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号量的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号量的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值