不使用pipe()获取两个文件描述符

3
我正在尝试理解命令pipe(2),例如:
int pipefd[2];
if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
}

我希望使用共享内存获取两个文件描述符,用于父子进程之间的匿名管道。例如,这是父进程和子进程之间的简单通信:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/wait.h>  
#include <unistd.h>    
#include <string.h>   
#define SHMSIZE 16
int main() {
   int shmid;
   char *shm;

   if(fork() == 0)   // child first

   {

      shmid = shmget(2009, SHMSIZE, 0);
      shm = shmat(shmid, 0, 0);
      char *s = (char *) shm;
      *s = '\0';
      int i;

      // child enters the input that would be stored in the shared memory
      for(i=0; i<3; i++) {
         int n;
         printf("Enter number<%i>: ", i);
         scanf("%d", &n);

         // convert the input into a c-string
         sprintf(s, "%s%d", s, n);
      }
      strcat(s, "\n");

      // display the contents of the shared memory
      printf ("I'm the child process , and I wrote:%s\n",shm);

      // detaches the shared memory segment
      shmdt(shm);
   }


   else   // parent

   {

       // get the segment
      shmid = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);

      // attaching the segment to the father
      shm = shmat(shmid, 0, 0);

      // father waits for the son the finish
      wait(NULL);

      // father displays what the son wrote
      printf ("I'm the father , and my child wrote :%s\n",shm) ;

      // detaches the shared memory segment
      shmdt(shm);

      shmctl(shmid, IPC_RMID, NULL);
   }
   return 0;
}

输出结果非常简单:
Enter number<0>: 123
Enter number<1>: 567
Enter number<2>: 789
I'm the child process , and I wrote:123567789

I'm the father , and my child wrote :123567789

这是我对 shm_pipe_pipe() 的实现,它可以替代 pipe(2)
int shm_pipe_pipe(int spd[2])
{
    spd[0] = shmget(2009, SHMSIZE, 0);
    spd[1] = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);

     if (spd[0] == -1 || spd[1] == -1)
         return -1;
     return 1;

}

我的问题是:

  1. 我理解fd[0]用于读取,fd[1]用于写入,但它们确切地保存了什么?地址?

  2. 我编写的函数shm_pipe_pipe似乎无法正常工作,出了什么问题?

谢谢。


回答你的第一个问题,它们保存文件描述符。它们不是地址,而是表中的索引。这个表实际上包含了内核将要解释的地址(如果描述符是磁盘文件,则为文件系统;如果是网络套接字,则为网络堆栈等)。文件的用户永远不会得到实际地址,只会得到包含实际地址的表的索引。这个表的结构非常复杂,并且在各个子系统中有所不同,但基本概念相对来说是相同的。 - Aftnix
shmget() 的返回值不是文件描述符,而是内存段标识符。因此,你的实现可能无法正常工作。 - Aftnix
1个回答

8
  1. 对于你的第一个问题,它们保存文件描述符。它们不是地址,而是指向表格的索引。这个表格实际上有地址,内核将解释这些地址(如果描述符是磁盘文件,则为文件系统;如果是网络套接字,则为网络堆栈等)。文件的用户从未得到实际地址,只有包含实际地址的表格中给出的索引。这个表格的结构非常复杂,在各个子系统中也不同。但基本概念相对一致。

下面的图片适用于磁盘文件。

This image is valid for disk files

在两个独立进程的情况下:

Two independent process

在管道的情况下...

This is how Pipe works, its just two file descriptors

这是文件描述符表的一般视图 FDT

  1. 关于你的第二个问题,看起来你正在试图从 shmget() 返回的值中填充文件描述符数组 pipefd

man 2 shmget

shmget() 返回与参数 key 的值关联的共享内存标识符。如果 key 的值为 IPC_PRIVATE 或 key 不是 IPC_PRIVATE,没有对应于 key 的共享内存段,并且 shmflg 指定了 IPC_CREAT,则创建大小等于将 size 舍入到 PAGE_SIZE 的倍数的共享内存段。

理解 shmget() 做什么很重要。它请求一个舍入后的大小(使用系统的 PAGE_SIZE)。这个内存段由一个键来标识。因此内核维护一个键值表。这个表还包含共享内存区域的地址。现在,当你调用 shmat() 时,你使用 shmid 查询这个表,返回的地址会附加到你的进程地址空间中。

所以 shmget() 返回的 shmid 是系统维护的键值对表中的一个条目,与 pipe() 使用的文件描述符无关。

底线是:

你不能以这种方式实现 pipe。为什么不看一下 pipe 实际的实现呢?

尽管该文件中没有有用的内容,因为它只调用其他__pipe。您可以克隆一个git仓库,并使用cscope查找pipe的实际实现。

http://sourceware.org/git/?p=glibc.git;a=blob;f=io/pipe.c;h=07a37ae778046e56c13e62fd2a2fa678f5546424;hb=HEAD


首先,为您的出色回答点赞。其次,我必须使用(如上所述的)共享内存,因此我需要用共享内存实现对pipe(2)的替换。您能否请解释一下我该如何修复上面的方法? - JAN
我正在尝试弄清楚glibc如何实现pipe()。如果我有所发现,我会更新我的答案。 - Aftnix
1
glibc将pipe实现为(本质上是)syscall(SYS_pipe, pipefds); - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接