linux下进程通信

管道被看作是打开的文件,但在已安装的文件系统中没有相应的映像。

POSIX只定义了半双工的管道,因此即使pipe()系统调用返回了两个描述符,每个进程在使用一个文件描述符之前仍得把另外一个文件描述符关闭。

在linux中,popen()和pclose()都包含在C函数库里面。

在popen()函数被调用之后,父进程和子进程就可以通过管道交换信息;父进程可以使用该函数调用所返回的FILE指针来读写数据。子进程所执行的程序分别把数据写入标准输出或标准输入中读写数据。

对每个管道来说,内核都要创建一个索引节点和两个文件对象,一个文件对象用于读,一个文件对象用于写。

当索引节点指的是管道时,其i_pipe字段指向一个pipe_inde_info的结构,除了一个索引节点对象和两个文件对象之外,每个管道都还有自己的管道缓冲区(pipe_buffer),实际上,它就是一个单独的页。

管道是作为一组VFS对象来实现的,因此没有对应的磁盘映像。????

init_pipe_fs()函数注册pipefs文件系统并安装它。

static struct file_system_type pipe_fs_type = {
     .name       = "pipefs",
     .mount      = pipefs_mount,
     .kill_sb    = kill_anon_super,
 };
 
 static int __init init_pipe_fs(void)
 {
     int err = register_filesystem(&pipe_fs_type);
 
     if (!err) {
         pipe_mnt = kern_mount(&pipe_fs_type);
         if (IS_ERR(pipe_mnt)) {
             err = PTR_ERR(pipe_mnt);
             unregister_filesystem(&pipe_fs_type);
         }
     }
     return err;
 }
pipe()系统调用由下面函数来处理:

SYSCALL_DEFINE1(pipe, int __user *, fildes)
 {
     return sys_pipe2(fildes, 0);
 }

 SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
 {
     int fd[2];
     int error;
 
     error = do_pipe_flags(fd, flags);
     if (!error) {
         if (copy_to_user(fildes, fd, sizeof(fd))) {
             sys_close(fd[0]);
             sys_close(fd[1]);
             error = -EFAULT;
         }
     }
     return error;
 }

管道读操作是可以非阻塞的。在这种情况下,只要所有可用的字节(即使是0个)一被拷贝到用户地址空间,读操作就完成。

只要在管道为空而且当前没有进程正在使用与管道的协同到相关的文件对象时,read()系统调用才会返回0.

如果两个或多个进程并发地写入一个管道,那么任何少于4096字节(管道缓冲大小)的写操作都必须单独完成,而不能与唯一进程对同一个管道的写操作交叉进行。但是超过4096个字节的写操作是可分割的,也可以强制调用进程睡眠。

如果管道没有读进程(也就是说如果管道的索引节点对象的readers字段的值是0),那么任何对管道执行的写操作都会失败。在这种情况下,内核会向写进程发送一个SIGPIPE信号,并停止write()系统调用,使其返回一个-EPIPE错误码,这个错误码就是我们熟悉的“Broken pipe”的消息。

任意的两个进程不可能共享同一个管道,除非管道由一个共同的祖先进程创建。

FIFO在几个方面和管道类似:在文件系统中不拥有磁盘块,打开的FIFO总是与一个内核缓冲区相关联,这一缓冲区临时存放两个或多个进程之间交换的数据。

有两个主要的差别:FIFO索引节点出现在系统目录树上而不是pipefs特殊文件系统中;FIFO是一种双向通信管道,也就是说可以以读写模式打开一个FIFO。

FIFO一旦被创建,就可以使用普通的open(),read(),write(),close()系统调用访问fifo,但是VFS对FIFO的处理方法比较特殊,因为FIFO的索引节点及文件操作都是专用的,并且不依赖于FIFO所在的文件系统。?????

IPC数据结构是在进程请求IPC资源(信号量,消息队列或者共享内存区)时动态创建的。每个IPC资源都是持久的:除非被进程显示地释放,否则永远驻留在内存中(直到系统关闭)。IPC资源可以由任一进程使用,包括那些不共享祖先进程所创建的资源的进程。

每个新资源都有一个IPC关键字来标识的,IPC标识符由内核分配给IPC资源,在系统内部是唯一的,而IPC关键字可以由程序员自由选择。

当两个或更多的进程要通过一个IPC资源进程通信时,这些进程都要引用该资源的IPC标识符。

根据新资源是信号量,消息队列还是共享内存区,分别调用semget(),msgget()或shmget()函数创建IPC资源。

IPC资源的每种类型都拥有ipc_ids数据结构,每个kern_ipc_perm数据结构与一个IPC资源相关联。

 SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second,
         unsigned long, third, void __user *, ptr, long, fifth)
 {
     int version, ret;
 
     version = call >> 16; /* hack for backward compatibility */
     call &= 0xffff;
 
     switch (call) {
     case SEMOP:
         return sys_semtimedop(first, (struct sembuf __user *)ptr,
                       second, NULL);
     case SEMTIMEDOP:
         return sys_semtimedop(first, (struct sembuf __user *)ptr,
                       second,
                       (const struct timespec __user *)fifth);
 
     case SEMGET:
         return sys_semget(first, second, third);
     case SEMCTL: {
         union semun fourth;
         if (!ptr)
             return -EINVAL;
         if (get_user(fourth.__pad, (void __user * __user *) ptr))
             return -EFAULT;
         return sys_semctl(first, second, third, fourth);
     }
 
     case MSGSND:
         return sys_msgsnd(first, (struct msgbuf __user *) ptr,
                   second, third);
     case MSGRCV:
         switch (version) {
         case 0: {
             struct ipc_kludge tmp;
             if (!ptr)
                 return -EINVAL;
 
           if (copy_from_user(&tmp,
                        (struct ipc_kludge __user *) ptr,
                        sizeof(tmp)))
                 return -EFAULT;
             return sys_msgrcv(first, tmp.msgp, second,
                        tmp.msgtyp, third);
         }
         default:
             return sys_msgrcv(first,
                        (struct msgbuf __user *) ptr,
                        second, fifth, third);
         }
     case MSGGET:
         return sys_msgget((key_t) first, second);
     case MSGCTL:
         return sys_msgctl(first, second, (struct msqid_ds __user *)ptr);
 
     case SHMAT:
         switch (version) {
         default: {
             unsigned long raddr;
             ret = do_shmat(first, (char __user *)ptr,
                        second, &raddr);
             if (ret)
                 return ret;
             return put_user(raddr, (unsigned long __user *) third);
         }
         case 1:
             /*
              * This was the entry point for kernel-originating calls
              * from iBCS2 in 2.2 days.
              */
             return -EINVAL;
         }
     case SHMDT:
         return sys_shmdt((char __user *)ptr);
     case SHMGET:
        return sys_shmget(first, second, third);
     case SHMCTL:
         return sys_shmctl(first, second,
                    (struct shmid_ds __user *) ptr);
     default:
         return -ENOSYS;
      }
 }

如果受保护的资源是可用的,那么信号量的值就是正数,如果受保护的资源现在不可用,那么信号量的值就是0。要访问资源的进程试图把信号量的值减1,但是,内核阻塞这个进程,知道在这个信号量上的操作产生一个正值,当进程释放受保护的进程资源时,就把信号量的值增加1.

1调用semget()封装函数来获得IPC信号量标识符,作为参数指定对共享资源进行保护的IPC信号量的IPC关键字 2调用semop()封装函数来测试并递减所有原始信号量所涉及的值。如果所有的测试全部成功,就执行递减操作,结束函数并允许这个进程访问受保护的资源。 3当放弃受保护的资源时,就再次调用semop函数来原子地增加所有有关的原始信号量 4作为选择,调用semctl()封装函数,在参数中,指定IPC_RMID命令把这个IPC信号量从系统中删除。

内核给每个IPC信号量都分配了一个挂起信号队列,用来标识正在等待数组中的一个(或多个)信号量的进程,这个队列是一个sem_queue数据结构的双向链表。

struct semid_ds {
      struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
      __kernel_time_t sem_otime;      /* last semop time */
      __kernel_time_t sem_ctime;      /* last change time */
      struct sem  *sem_base;      /* ptr to first semaphore in array */
      struct sem_queue *sem_pending;      /* pending operations to be processe    d */
      struct sem_queue **sem_pending_last;    /* last pending operation */
      struct sem_undo *undo;          /* undo requests on this array */
      unsigned short  sem_nsems;      /* no. of semaphores in array */
  };

/* One queue for each sleeping process in the system. */
 struct sem_queue {
     struct list_head    simple_list; /* queue of pending operations */
     struct list_head    list;    /* queue of pending operations */
     struct task_struct  *sleeper; /* this process */
     struct sem_undo     *undo;   /* undo structure */
     int             pid;     /* process id of requesting process */
     int             status;  /* completion status of operation */
     struct sembuf       *sops;   /* array of pending operations */
     int         nsops;   /* number of operations */
     int         alter;   /* does the operation alter the array? */
 };

消息是由固定大小的首部和可变长度的正文组成,可以使用一个整数值(消息类型)标识消息,这就允许进程有选择地从消息队列中获取消息。只要进程从IPC消息队列中读出一条消息,内核就把这个消息删除。因此,只能有一个进程接收一条给定的消息。

每条消息分开存放在一个或多个动态分配的页中。第一页的起始部分存放消息头,消息头是一个msg_msg类型的数据结构。

 /* one msg_msg structure for each message */
  struct msg_msg {
      struct list_head m_list;
      long  m_type;          
      int m_ts;           /* message text size */
      struct msg_msgseg* next;
      void *security;
      /* the actual message follows immediately */
  };

如果进程要访问这种存放在共享内存区的数据结构,就必须在自己的地址空间中增加一个新内存区,它将映射与这个共享内存区相关的页框,这样的页框可以很容易地由内核通过请求调页进行处理。

调用shmat()函数把一个共享内存区“附加”到一个进程上。调用进程可以获得这个内存区域的起始线性地址,但是这个地址通常并不重要,访问这个共享内存区域的每个进程都可以使用自己地址空间的不同地址。

每个IPC共享内存区与属于shm特殊文件系统的一个普通文件相关联。

因为shm文件系统在系统目录树中没有安装点,因此用户不能通过普通的VFS系统调用打开并访问它的文件。但是只要进程“附加”一个内存段,内核就调用do_mmap(),并在进程的地址空间创建文件的一个新的共享内存映射。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值