一. 前言
上文中我們介紹了進(jìn)程間通信的方法之一:信號(hào),本文將繼續(xù)介紹另一種進(jìn)程間通信的方法,即管道。管道是Linux中使用shell經(jīng)常用到的一個(gè)技術(shù),本文將深入剖析管道的實(shí)現(xiàn)和運(yùn)行邏輯。
二. 管道簡(jiǎn)介
在Linux的日常使用中,我們常常會(huì)用到管道,如下所示
ps -ef | grep 關(guān)鍵字 | awk '{print $2}' | xargs kill -9
這里面的豎線|就是一個(gè)管道。它會(huì)將前一個(gè)命令的輸出,作為后一個(gè)命令的輸入。從管道的這個(gè)名稱可以看出來(lái),管道是一種單向傳輸數(shù)據(jù)的機(jī)制,它其實(shí)是一段緩存,里面的數(shù)據(jù)只能從一端寫入,從另一端讀出。如果想互相通信,我們需要?jiǎng)?chuàng)建兩個(gè)管道才行。
管道分為兩種類型,| 表示的管道稱為匿名管道,意思就是這個(gè)類型的管道沒(méi)有名字,用完了就銷毀了。就像上面那個(gè)命令里面的一樣,豎線代表的管道隨著命令的執(zhí)行自動(dòng)創(chuàng)建、自動(dòng)銷毀。用戶甚至都不知道自己在用管道這種技術(shù),就已經(jīng)解決了問(wèn)題。另外一種類型是命名管道。這個(gè)類型的管道需要通過(guò) mkfifo 命令顯式地創(chuàng)建。
mkfifo hello
我們可以往管道里面寫入東西。例如,寫入一個(gè)字符串。
# echo "hello world" > hello
這個(gè)時(shí)候管道里面的內(nèi)容沒(méi)有被讀出,這個(gè)命令就會(huì)停在這里。這個(gè)時(shí)候,我們就需要重新連接一個(gè)終端。在終端中用下面的命令讀取管道里面的內(nèi)容:
# cat < hello hello world
一方面,我們能夠看到,管道里面的內(nèi)容被讀取出來(lái),打印到了終端上;另一方面,echo 那個(gè)命令正常退出了。這就是有名管道的執(zhí)行流程。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【865977150】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦??!

三. 匿名管道創(chuàng)建
實(shí)際管道的創(chuàng)建調(diào)用的是系統(tǒng)調(diào)用pipe(),該函數(shù)建了一個(gè)管道 pipe,返回了兩個(gè)文件描述符,這表示管道的兩端,一個(gè)是管道的讀取端描述符 fd[0],另一個(gè)是管道的寫入端描述符 fd[1]。
int pipe(int fd[2])
其內(nèi)核實(shí)現(xiàn)如下所示,pipe2 ()調(diào)用 __do_pipe_flags() 創(chuàng)建一個(gè)數(shù)組 files來(lái)存放管道的兩端的打開(kāi)文件,另一個(gè)數(shù)組 fd 存放管道的兩端的文件描述符。如果 __do_pipe_flags() 沒(méi)有錯(cuò)誤,那就調(diào)用fd_install()將兩個(gè)fd和兩個(gè)struct file關(guān)聯(lián)起來(lái),這一點(diǎn)和打開(kāi)一個(gè)文件的過(guò)程類似。
SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
return sys_pipe2(fildes, 0);
}
SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
{
struct file *files[2];
int fd[2];
int error;
error = __do_pipe_flags(fd, files, flags);
if (!error) {
if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) {
......
error = -EFAULT;
} else {
fd_install(fd[0], files[0]);
fd_install(fd[1], files[1]);
}
}
return error;
}
__do_pipe_flags()調(diào)用了create_pipe_files()生成fd,然后調(diào)用get_unused_fd_flags()賦值fdr和fdw,即讀文件描述符和寫文件描述符。由此也可以看出管道的特性:由一端寫入,由另一端讀出。
static int __do_pipe_flags(int *fd, struct file **files, int flags)
{
int error;
int fdw, fdr;
......
error = create_pipe_files(files, flags);
......
error = get_unused_fd_flags(flags);
......
fdr = error;
error = get_unused_fd_flags(flags);
......
fdw = error;
audit_fd_pair(fdr, fdw);
fd[0] = fdr;
fd[1] = fdw;
return 0;
......
}
create_pipe_files()是管道創(chuàng)建的關(guān)鍵邏輯,從這里可以看出來(lái)管道實(shí)際上也是一種抽象的文件系統(tǒng)pipefs,有著對(duì)應(yīng)的特殊文件以及inode。這里首先通過(guò)get_pipe_inode()獲取特殊inode,然后調(diào)用alloc_file_pseudo()通過(guò)inode以及對(duì)應(yīng)的掛載結(jié)構(gòu)體pipe_mnt,文件操作結(jié)構(gòu)體pipefifo_fops創(chuàng)建關(guān)聯(lián)的dentry并以此創(chuàng)建文件結(jié)構(gòu)體并分配內(nèi)存,通過(guò)alloc_file_clone()創(chuàng)建一份新的file后將兩個(gè)文件分別保存在res[0]和res[1]中。
int create_pipe_files(struct file **res, int flags)
{
struct inode *inode = get_pipe_inode();
struct file *f;
if (!inode)
return -ENFILE;
f = alloc_file_pseudo(inode, pipe_mnt, "",
O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT)),
&pipefifo_fops);
if (IS_ERR(f)) {
free_pipe_info(inode->i_pipe);
iput(inode);
return PTR_ERR(f);
}
f->private_data = inode->i_pipe;
res[0] = alloc_file_clone(f, O_RDONLY | (flags & O_NONBLOCK),
&pipefifo_fops);
if (IS_ERR(res[0])) {
put_pipe_info(inode, inode->i_pipe);
fput(f);
return PTR_ERR(res[0]);
}
res[0]->private_data = inode->i_pipe;
res[1] = f;
return 0;
}
其虛擬文件系統(tǒng)pipefs對(duì)應(yīng)的結(jié)構(gòu)體和操作如下:
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);
}
......
}
const struct file_operations pipefifo_fops = {
.open = fifo_open,
.llseek = no_llseek,
.read_iter = pipe_read,
.write_iter = pipe_write,
.poll = pipe_poll,
.unlocked_ioctl = pipe_ioctl,
.release = pipe_release,
.fasync = pipe_fasync,
};
static struct inode * get_pipe_inode(void)
{
struct inode *inode = new_inode_pseudo(pipe_mnt->mnt_sb);
struct pipe_inode_info *pipe;
......
inode->i_ino = get_next_ino();
pipe = alloc_pipe_info();
......
inode->i_pipe = pipe;
pipe->files = 2;
pipe->readers = pipe->writers = 1;
inode->i_fop = &pipefifo_fops;
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);
return inode;
......
}
至此,一個(gè)匿名管道就創(chuàng)建成功了。如果對(duì)于 fd[1]寫入,調(diào)用的是 pipe_write(),向 pipe_buffer 里面寫入數(shù)據(jù);如果對(duì)于 fd[0]的讀入,調(diào)用的是 pipe_read(),也就是從 pipe_buffer 里面讀取數(shù)據(jù)。至此,我們?cè)谝粋€(gè)進(jìn)程內(nèi)創(chuàng)建了管道,但是尚未實(shí)現(xiàn)進(jìn)程間通信。
四. 匿名管道通信
在上文中我們提到了匿名管道通過(guò)|符號(hào)實(shí)現(xiàn)進(jìn)程間的通信,傳遞輸入給下一個(gè)進(jìn)程作為輸出,其實(shí)現(xiàn)原理如下:
- 利用fork創(chuàng)建子進(jìn)程,復(fù)制file_struct會(huì)同樣復(fù)制fd輸入輸出數(shù)組,但是fd指向的文件僅有一份,即兩個(gè)進(jìn)程間可以通過(guò)fd數(shù)組實(shí)現(xiàn)對(duì)同一個(gè)管道文件的跨進(jìn)程讀寫操作
- 禁用父進(jìn)程的讀,禁用子進(jìn)程的寫,即從父進(jìn)程寫入從子進(jìn)程讀出,從而實(shí)現(xiàn)了單向管道,避免了混亂
- 對(duì)于A|B來(lái)說(shuō),shell首先創(chuàng)建子進(jìn)程A,接著創(chuàng)建子進(jìn)程B,由于二者均從shell創(chuàng)建,因此共用fd數(shù)組。shell關(guān)閉讀寫,A開(kāi)寫B(tài)開(kāi)讀,從而實(shí)現(xiàn)了A 和B之間的通信。

接著我們需要調(diào)用dup2()實(shí)現(xiàn)輸入輸出和管道兩端的關(guān)聯(lián),該函數(shù)會(huì)將fd賦值給fd2
/* Duplicate FD to FD2, closing the old FD2 and making FD2 be
open the same file as FD is. Return FD2 or -1. */
int
__dup2 (int fd, int fd2)
{
if (fd < 0 || fd2 < 0)
{
__set_errno (EBADF);
return -1;
}
if (fd == fd2)
/* No way to check that they are valid. */
return fd2;
__set_errno (ENOSYS);
return -1;
}
在 files_struct 里面,有這樣一個(gè)表,下標(biāo)是 fd,內(nèi)容指向一個(gè)打開(kāi)的文件 struct file。在這個(gè)表里面,前三項(xiàng)是定下來(lái)的,其中第零項(xiàng) STDIN_FILENO 表示標(biāo)準(zhǔn)輸入,第一項(xiàng) STDOUT_FILENO 表示標(biāo)準(zhǔn)輸出,第三項(xiàng) STDERR_FILENO 表示錯(cuò)誤輸出。
struct files_struct {
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
}
- 在 A 進(jìn)程寫入端通過(guò)dup2(fd[1],STDOUT_FILENO)將 STDOUT_FILENO(也即第一項(xiàng))不再指向標(biāo)準(zhǔn)輸出,而是指向創(chuàng)建的管道文件,那么以后往標(biāo)準(zhǔn)輸出寫入的任何東西,都會(huì)寫入管道文件。
- 在 B 進(jìn)程中讀取端通過(guò)dup2(fd[0],STDIN_FILENO)將 STDIN_FILENO 也即第零項(xiàng)不再指向標(biāo)準(zhǔn)輸入,而是指向創(chuàng)建的管道文件,那么以后從標(biāo)準(zhǔn)輸入讀取的任何東西,都來(lái)自于管道文件。
至此,我們將 A|B 的功能完成。

五. 有名管道
對(duì)于有名管道,我們需要通過(guò)mkfifo創(chuàng)建,實(shí)際調(diào)用__xmknod()函數(shù),最終調(diào)用mknod(),和字符設(shè)備創(chuàng)建一樣。
/* Create a named pipe (FIFO) named PATH with protections MODE. */
int
mkfifo (const char *path, mode_t mode)
{
dev_t dev = 0;
return __xmknod (_MKNOD_VER, path, mode | S_IFIFO, &dev);
}
/* Create a device file named PATH, with permission and special bits MODE
and device number DEV (which can be constructed from major and minor
device numbers with the `makedev' macro above). */
int
__xmknod (int vers, const char *path, mode_t mode, dev_t *dev)
{
unsigned long long int k_dev;
if (vers != _MKNOD_VER)
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
/* We must convert the value to dev_t type used by the kernel. */
k_dev = (*dev) & ((1ULL << 32) - 1);
if (k_dev != *dev)
return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);
return INLINE_SYSCALL (mknod, 3, path, mode, (unsigned int) k_dev);
}
mknod 在字符設(shè)備那一節(jié)已經(jīng)解析過(guò)了,先是通過(guò) user_path_create() 對(duì)于這個(gè)管道文件創(chuàng)建一個(gè) dentry,然后因?yàn)槭?S_IFIFO,所以調(diào)用 vfs_mknod()。由于這個(gè)管道文件是創(chuàng)建在一個(gè)普通文件系統(tǒng)上的,假設(shè)是在 ext4 文件上,于是 vfs_mknod 會(huì)調(diào)用 ext4_dir_inode_operations 的 mknod,也即會(huì)調(diào)用 ext4_mknod()。
在 ext4_mknod() 中,ext4_new_inode_start_handle() 會(huì)調(diào)用 __ext4_new_inode(),在 ext4 文件系統(tǒng)上真的創(chuàng)建一個(gè)文件,但是會(huì)調(diào)用 init_special_inode(),創(chuàng)建一個(gè)內(nèi)存中特殊的 inode,這個(gè)函數(shù)我們?cè)谧址O(shè)備文件中也遇到過(guò),只不過(guò)當(dāng)時(shí) inode 的 i_fop 指向的是 def_chr_fops,這次換成管道文件了,inode 的 i_fop 變成指向 pipefifo_fops,這一點(diǎn)和匿名管道是一樣的。這樣,管道文件就創(chuàng)建完畢了。
接下來(lái),要打開(kāi)這個(gè)管道文件,我們還是會(huì)調(diào)用文件系統(tǒng)的 open() 函數(shù)。還是沿著文件系統(tǒng)的調(diào)用方式,一路調(diào)用到 pipefifo_fops 的 open() 函數(shù),也就是 fifo_open()。在 fifo_open() 里面會(huì)創(chuàng)建 pipe_inode_info,這一點(diǎn)和匿名管道也是一樣的。這個(gè)結(jié)構(gòu)里面有個(gè)成員是 struct pipe_buffer *bufs。我們可以知道,所謂的命名管道,其實(shí)是也是內(nèi)核里面的一串緩存。接下來(lái),對(duì)于命名管道的寫入,我們還是會(huì)調(diào)用 pipefifo_fops 的 pipe_write() 函數(shù),向 pipe_buffer 里面寫入數(shù)據(jù)。對(duì)于命名管道的讀入,我們還是會(huì)調(diào)用 pipefifo_fops 的 pipe_read(),也就是從 pipe_buffer 里面讀取數(shù)據(jù)。
static int fifo_open(struct inode *inode, struct file *filp)
{
struct pipe_inode_info *pipe;
bool is_pipe = inode->i_sb->s_magic == PIPEFS_MAGIC;
int ret;
filp->f_version = 0;
spin_lock(&inode->i_lock);
if (inode->i_pipe) {
pipe = inode->i_pipe;
pipe->files++;
spin_unlock(&inode->i_lock);
} else {
spin_unlock(&inode->i_lock);
pipe = alloc_pipe_info();
if (!pipe)
return -ENOMEM;
pipe->files = 1;
spin_lock(&inode->i_lock);
if (unlikely(inode->i_pipe)) {
inode->i_pipe->files++;
spin_unlock(&inode->i_lock);
free_pipe_info(pipe);
pipe = inode->i_pipe;
} else {
inode->i_pipe = pipe;
spin_unlock(&inode->i_lock);
}
}
filp->private_data = pipe;
/* OK, we have a pipe and it's pinned down */
__pipe_lock(pipe);
/* We can only do regular read/write on fifos */
filp->f_mode &= (FMODE_READ | FMODE_WRITE);
switch (filp->f_mode) {
case FMODE_READ:
/*
* O_RDONLY
* POSIX.1 says that O_NONBLOCK means return with the FIFO
* opened, even when there is no process writing the FIFO.
*/
pipe->r_counter++;
if (pipe->readers++ == 0)
wake_up_partner(pipe);
if (!is_pipe && !pipe->writers) {
if ((filp->f_flags & O_NONBLOCK)) {
/* suppress EPOLLHUP until we have
* seen a writer */
filp->f_version = pipe->w_counter;
} else {
if (wait_for_partner(pipe, &pipe->w_counter))
goto err_rd;
}
}
break;
case FMODE_WRITE:
/*
* O_WRONLY
* POSIX.1 says that O_NONBLOCK means return -1 with
* errno=ENXIO when there is no process reading the FIFO.
*/
ret = -ENXIO;
if (!is_pipe && (filp->f_flags & O_NONBLOCK) && !pipe->readers)
goto err;
pipe->w_counter++;
if (!pipe->writers++)
wake_up_partner(pipe);
if (!is_pipe && !pipe->readers) {
if (wait_for_partner(pipe, &pipe->r_counter))
goto err_wr;
}
break;
case FMODE_READ | FMODE_WRITE:
/*
* O_RDWR
* POSIX.1 leaves this case "undefined" when O_NONBLOCK is set.
* This implementation will NEVER block on a O_RDWR open, since
* the process can at least talk to itself.
*/
pipe->readers++;
pipe->writers++;
pipe->r_counter++;
pipe->w_counter++;
if (pipe->readers == 1 || pipe->writers == 1)
wake_up_partner(pipe);
break;
default:
ret = -EINVAL;
goto err;
}
/* Ok! */
__pipe_unlock(pipe);
return 0;
......
}
總結(jié)
無(wú)論是匿名管道還是命名管道,在內(nèi)核都是一個(gè)文件。只要是文件就要有一個(gè) inode。在這種特殊的 inode 里面,file_operations 指向管道特殊的 pipefifo_fops,這個(gè) inode 對(duì)應(yīng)內(nèi)存里面的緩存。當(dāng)我們用文件的 open 函數(shù)打開(kāi)這個(gè)管道設(shè)備文件的時(shí)候,會(huì)調(diào)用 pipefifo_fops 里面的方法創(chuàng)建 struct file 結(jié)構(gòu),他的 inode 指向特殊的 inode,也對(duì)應(yīng)內(nèi)存里面的緩存,file_operations 也指向管道特殊的 pipefifo_fops。寫入一個(gè) pipe 就是從 struct file 結(jié)構(gòu)找到緩存寫入,讀取一個(gè) pipe 就是從 struct file 結(jié)構(gòu)找到緩存讀出。匿名管道和命名管道區(qū)別就在于匿名管道會(huì)通過(guò)dup2()指定輸入輸出源,完成之后立即釋放,而命名管道通過(guò)mkfifo創(chuàng)建掛載后,需要手動(dòng)調(diào)用pipe_read()和pipe_write()來(lái)完成其功能,表現(xiàn)到用戶端即為前面提到的例子。
審核編輯:湯梓紅-
Linux
+關(guān)注
關(guān)注
88文章
11755瀏覽量
218995 -
管道
+關(guān)注
關(guān)注
3文章
148瀏覽量
18376 -
進(jìn)程間通信
+關(guān)注
關(guān)注
0文章
16瀏覽量
2585
發(fā)布評(píng)論請(qǐng)先 登錄
Linux進(jìn)程間通信(IPC)全解析:從管道到?Socket,一篇講透
Linux下進(jìn)程間通信
【Linux學(xué)習(xí)雜談】之進(jìn)程通信
怎樣通過(guò)匿名管道去實(shí)現(xiàn)進(jìn)程間的通信呢
進(jìn)程間通信之:管道
進(jìn)程間通信之Linux下進(jìn)程間通信概述
Linux進(jìn)程間通信
Linux進(jìn)程間通信方式——管道
嵌入式Linux進(jìn)程 -進(jìn)程間通信
常見(jiàn)的進(jìn)程間通信方式
如何實(shí)現(xiàn)一套linux進(jìn)程間通信的機(jī)制
Linux進(jìn)程間通信方法之管道
評(píng)論