管道被看作是打开的文件,但在已安装的文件系统中没有相应的映像。
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;
}
只要在管道为空而且当前没有进程正在使用与管道的协同到相关的文件对象时,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;
}
}
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? */
};
每条消息分开存放在一个或多个动态分配的页中。第一页的起始部分存放消息头,消息头是一个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(),并在进程的地址空间创建文件的一个新的共享内存映射。