本文共 6590 字,大约阅读时间需要 21 分钟。
管道分为有名管道和无名管道,无名管道主要用于带亲缘关系间的进程之间的通信,有名管道可以用于任意两个进程之间的通信。
管道的通信方式:半双工的方式可以看到创建管道的内核代码中有一个inode 节点,两个file 节点
struct inode 节点中记录了文件在存储介质上的位置以及分布的信息,即:一个inode对应一个物理上的具体的文件
struct file 是一个文件描述符。每一个进程的pcb 块中都有一个专门存放文件描述符的数组 struct dentry 节点中记录了一个文件的逻辑信息,该结构体中有一个指针i_inode可以指向一个struct inode 结构,可以有多个文件名不同的文件来指向同一个struct inode,即:多个struct dentry 中的i_inode指向同一个struct inodeint do_pipe(int *fd){ struct qstr this; char name[32]; struct dentry *dentry; struct inode * inode; struct file *f1, *f2; int error; int i,j;
进程对每个已经打开的文件的操作都是通过file 数据结构进行的,只有在同一个进程按相同的模式打开一个文件时才能共享同一个数据结构。管道实际上是一个无形的文件,对这个文件的操作要通过两个已打开的文件进行,分别代表文件的两端,所以管道的两端不共享同一个file数据结构,所以要调用get_empty_file()为管道的两端各分配一个file数据结构。
同时,每个文件都是由一个inode结构代表的,虽然管道是一个无形的 文件,它也要有一个inode 结构,所以需要在创建临时管道的时候调用get_pipe_inode()创建一个inode 结构,inode 结构中有一个i_pipe指针,指向pipe_inode_info数据结构,只有inode所代表的文件是一个管道的时候才能用到该指针。 创建了inode结构后,调用pipe_new()分配所需要的缓冲区。再分配一个缓冲区作pipe_inode_info 数据结构看一下分配的过程
//获取两个文件描述符 f1 = get_empty_filp(); if (!f1) goto no_files; f2 = get_empty_filp(); if (!f2) goto close_f1; /** * get_pipe_inode为pipefs文件系统中的管道分配一个索引节点对象并对其进行初始化。 * 它会考虑一些资源限制,所以不一定能够分配成功。 */ inode = get_pipe_inode(); if (!inode) goto close_f12; /** * 为读和写分配文件对象和文件描述符。存放到i,j中 * 并将f1,f2设为只读和只写。 * 管道的两端只能有一端进行读另一端进行写 */ error = get_unused_fd(); if (error < 0) goto close_f12_inode; i = error; error = get_unused_fd(); if (error < 0) goto close_f12_inode_i; j = error; error = -ENOMEM; sprintf(name, "[%lu]", inode->i_ino); this.name = name; this.len = strlen(name); this.hash = inode->i_ino; /* will go */ /** * 分配一个目录项对象,并使用它把两个文件对象和索引节点对象连接在一起。 * 然后把新的索引节点插入到pipefs特殊文件系统中。 */ dentry = d_alloc(pipe_mnt->mnt_sb->s_root, &this); if (!dentry) goto close_f12_inode_i_j; dentry->d_op = &pipefs_dentry_operations; d_add(dentry, inode); f1->f_vfsmnt = f2->f_vfsmnt = mntget(mntget(pipe_mnt)); f1->f_dentry = f2->f_dentry = dget(dentry); f1->f_mapping = f2->f_mapping = inode->i_mapping; /* read file */ f1->f_pos = f2->f_pos = 0; f1->f_flags = O_RDONLY; f1->f_op = &read_pipe_fops; f1->f_mode = FMODE_READ; f1->f_version = 0; /* write file */ f2->f_flags = O_WRONLY; f2->f_op = &write_pipe_fops; f2->f_mode = FMODE_WRITE; f2->f_version = 0; //将新产生的文件描述符加入到进程的管理数组中。 fd_install(i, f1); fd_install(j, f2); /** * 向用户返回文件描述符进行读写。 */ fd[0] = i; fd[1] = j; return 0;
get_pipe_inode 获取一个inode节点
static struct inode * get_pipe_inode(void){ /** * 在pipe_fs文件系统中分配一个新的索引结点。 */ struct inode *inode = new_inode(pipe_mnt->mnt_sb); if (!inode) goto fail_inode; /** * pipe_new分配pipe_inode_info数据结构,并把它的地址存放在索引节点的i_pipe字段。 */ if(!pipe_new(inode)) goto fail_iput; /** * 初始化inode->r_counter和w_counter为1。 */ PIPE_READERS(*inode) = PIPE_WRITERS(*inode) = 1; inode->i_fop = &rdwr_pipe_fops; /* * Mark the inode dirty from the very beginning, * that way it will never be moved to the dirty * list because "mark_inode_dirty()" will think * that it already _is_ on the dirty list. */ inode->i_state = I_DIRTY; inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_blksize = PAGE_SIZE; return inode;fail_iput: iput(inode);fail_inode: return NULL;}
get_empty_filp 分配一个pipefs文件系统的文件描述符
/** * get_empty_filp为pipefs文件系统中的管道分配一个索引节点对象并对其进行初始化。 */struct file *get_empty_filp(void){ static int old_max; struct file * f; /* * Privileged users can go above max_files */ if (files_stat.nr_files < files_stat.max_files || capable(CAP_SYS_ADMIN)) { /** * 分配文件索引结点 */ //高速缓存中获取一个内存块。 f = kmem_cache_alloc(filp_cachep, GFP_KERNEL); if (f) { memset(f, 0, sizeof(*f)); if (security_file_alloc(f)) { file_free(f); goto fail; } eventpoll_init_file(f); atomic_set(&f->f_count, 1); f->f_uid = current->fsuid; f->f_gid = current->fsgid; rwlock_init(&f->f_owner.lock); /* f->f_version: 0 */ INIT_LIST_HEAD(&f->f_list); f->f_maxcount = INT_MAX; return f; } } /* Ran out of filps - report that */ if (files_stat.max_files >= old_max) { printk(KERN_INFO "VFS: file-max limit %d reached\n", files_stat.max_files); old_max = files_stat.max_files; } else { /* Big problems... */ printk(KERN_WARNING "VFS: filp allocation failed\n"); }fail: return NULL;}
这样就完成了一个管道的创建。
sys_msgget():
用一个给定的键值创建一个报文队列,或者是给定一个键值,找到已经建立的报文队列。 这里有一个全局变量ipc_ids,其类型是msg_ids结构体类型,其作用就是用于管理所有的消息队列; msg_ids中的指针entries指向一个ipc_id结构数组,数组中的每个元素都是ipc_id 数据结构,结构中有个指针p,指向一个kem_ipc_erm数据结构, 由于kem_ipc_perm数据结构是报文队列头msg_queue数据结构内部的第一个成分,上述数组元素中的指针p实际指向一个报文队列,数组的大小决定了已经或可以建立的报文队列数量。 如果给定的键值不存在,sys_msgget()->newque()来创建一个消息队列,并在newque->ipc_addid()来返回一个标识符。msg_ids中的entries指向以标识号为下标的ipc_id结构数组,ipc_id内容只有一个指针,指向kern_ipc_perm结构,同时每个报文的第一个成分就是kern_ipc_perm结构,其起始地址与整个msg_queue结构的起始地址相同,所以就把特定的msg_queue根据标识号填入了ipc_id结构中,并返回标识号 Ipc_ids 中的字段size记录着ipc_id数组的大小。通过ipc_addid()->grow_ary() 调整数组的大小。 键值与标识符是两码事,与文件系统相比,键值类似于文件名,标识符类似于打开的文件号 寻找键值的大致的过程: 通过遍历找到文件的标识符。int ipc_findkey(struct ipc_ids *ids, key_t key){ int id; struct kern_ipc_perm *p; for(id = 0; id <= ids_max ; id++ ) { p = ids->entries[id].p; if(p == NULL) { continue; } if(key == p->key) { return id; } } return -1;}
Sys_msgsnd()报文发送:
函数通过结构体struct mshbuf 来存储要发送的数据,但是该数据结构本身在用户空间,分别调用get_user()和load_msg()从用户空间复制到系统空间,其中load_msg()还要再系统空间为此分配缓冲区,通过消息队列进行进程将的通信的时候,要进行四次的数据的复制的过程。(1、用户空间到内核空间的复制;2、内核空间到内存的复制;3、内存到内核空间的复制;4、内核空间到用户空间的复制;) 系统空间使用的是msg_msg结构,当msg_msg结构本身加上报文的大小小于一个页面时,报文紧跟在msg_msg后面,否则采用报文分段,将分布在不同页面的报文链接起来,除了第一个页面的开头是msg_msg结构之外,其他的开头都是一个msg_msgseg结构。 在将消息放入队列之前:先检查队列的标识号, ipcperms() 检查当前进程是否有权向这个队列发送报文,检查该报文队列的总容量,检查当前队列所能容纳的最多的报文个数,如果报文队列容量或者是容纳的个数超过了系统的设置的上限值,检查系统调用参数中的IPC_NOWAIT是否为1,如果是,就出错返回,否则就睡眠等待,在进入睡眠之前,还要将一个msg_sender数据结构挂入队列中的q_senders链 Sys_msgrcv()报文接收: 首先检查标识符和权限,与发送数据的一样,然后根据接收的准则查找合适类型的报文,找到报文后,检查用户程序给出的缓冲区的空间是否足够大,如果不够大,而用户持又不允许在报文尾部截掉一块的话,就出错返回,反之,就可以接收这个报文,如果此时存在因队列容量不足在发送进程链中等到的进程,就调用ss_wakeup()将进程唤醒。 如果队列中没有类型相匹配的报文,如果IPC_NOWAIT标志位1,就立即返回,否则,就睡眠等待,并将当前进程挂入q_receiver队列。在msg_receiver结构中还记录着所要接收的报文类型,缓冲区的大小,由于共享内存的地址映射到了多个进程中,在一个进程向共享内存写入数据时,在写入一部分数据时候另一个进程也向共享内存写入数据,这时候得到的数据是混乱的,所以在一个进程向共享内存写入数据的时候,其余进程不能访问共享内存,所以共享内存的出现伴随着信号量的出现。
共享内存是直接映射到进程的地址空间的,所以进程可以直接对 这部分内存的数据进行读写,所以通过共享内存进行进程间通信的只进行两次数据的拷贝,在进程的通信方式中是最快的。
与报文队列相似,参加共享内存的进程首先创建一块共享内存区,然后其他进程通过一个共同的键取得他的标识符,得到一个共享内存的标识号以后,每个进程就可以将此共享内存区映射到自己的虚拟地址空间,然后就可以像访问一般内存一样地访问这块内存了,退出对此共享内存的共享时,则结束映射。
共享内存区的文件是有形的,需要在文件中实际地存储数据,所以要落实到某个物理外设上才行,另一方面,共享内存只对系统的当前运行有意义,一旦关机就失去了意义而不应该存在,下一次安装应该从空白处开始,所以不能放在普通的文件系统中,所以,把共享内存区放在页面交换盘区中最合适。
进程可以直接把数据写到共享内存中,其他的进程从共享内存中读取可以直接读取数据,所以过程中只有两次数据的拷贝的过程,在进程间的通信的方式中是最快的方式。
转载地址:http://oanwi.baihongyu.com/