0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux進(jìn)程間通信方法之管道

書生途 ? 來源:書生途 ? 作者:書生途 ? 2022-05-14 15:47 ? 次閱讀

一. 前言

上文中我們介紹了進(jìn)程間通信的方法之一:信號,本文將繼續(xù)介紹另一種進(jìn)程間通信的方法,即管道。管道是Linux中使用shell經(jīng)常用到的一個技術(shù),本文將深入剖析管道的實(shí)現(xiàn)和運(yùn)行邏輯。

二. 管道簡介

在Linux的日常使用中,我們常常會用到管道,如下所示

ps -ef | grep 關(guān)鍵字 | awk '{print $2}' | xargs kill -9

這里面的豎線|就是一個管道。它會將前一個命令的輸出,作為后一個命令的輸入。從管道的這個名稱可以看出來,管道是一種單向傳輸數(shù)據(jù)的機(jī)制,它其實(shí)是一段緩存,里面的數(shù)據(jù)只能從一端寫入,從另一端讀出。如果想互相通信,我們需要創(chuàng)建兩個管道才行。

管道分為兩種類型,| 表示的管道稱為匿名管道,意思就是這個類型的管道沒有名字,用完了就銷毀了。就像上面那個命令里面的一樣,豎線代表的管道隨著命令的執(zhí)行自動創(chuàng)建、自動銷毀。用戶甚至都不知道自己在用管道這種技術(shù),就已經(jīng)解決了問題。另外一種類型是命名管道。這個類型的管道需要通過 mkfifo 命令顯式地創(chuàng)建。

mkfifo hello

我們可以往管道里面寫入東西。例如,寫入一個字符串。

# echo "hello world" > hello

這個時候管道里面的內(nèi)容沒有被讀出,這個命令就會停在這里。這個時候,我們就需要重新連接一個終端。在終端中用下面的命令讀取管道里面的內(nèi)容:

# cat < hello hello world

一方面,我們能夠看到,管道里面的內(nèi)容被讀取出來,打印到了終端上;另一方面,echo 那個命令正常退出了。這就是有名管道的執(zhí)行流程。

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【865977150】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!!

pYYBAGJ_XpaAG7NqAAEEq3aWrTw543.jpg

三. 匿名管道創(chuàng)建

實(shí)際管道的創(chuàng)建調(diào)用的是系統(tǒng)調(diào)用pipe(),該函數(shù)建了一個管道 pipe,返回了兩個文件描述符,這表示管道的兩端,一個是管道的讀取端描述符 fd[0],另一個是管道的寫入端描述符 fd[1]。

int pipe(int fd[2])

其內(nèi)核實(shí)現(xiàn)如下所示,pipe2 ()調(diào)用 __do_pipe_flags() 創(chuàng)建一個數(shù)組 files來存放管道的兩端的打開文件,另一個數(shù)組 fd 存放管道的兩端的文件描述符。如果 __do_pipe_flags() 沒有錯誤,那就調(diào)用fd_install()將兩個fd和兩個struct file關(guān)聯(lián)起來,這一點(diǎn)和打開一個文件的過程類似。

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)鍵邏輯,從這里可以看出來管道實(shí)際上也是一種抽象的文件系統(tǒng)pipefs,有著對應(yīng)的特殊文件以及inode。這里首先通過get_pipe_inode()獲取特殊inode,然后調(diào)用alloc_file_pseudo()通過inode以及對應(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)存,通過alloc_file_clone()創(chuàng)建一份新的file后將兩個文件分別保存在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對應(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;
......
}

至此,一個匿名管道就創(chuàng)建成功了。如果對于 fd[1]寫入,調(diào)用的是 pipe_write(),向 pipe_buffer 里面寫入數(shù)據(jù);如果對于 fd[0]的讀入,調(diào)用的是 pipe_read(),也就是從 pipe_buffer 里面讀取數(shù)據(jù)。至此,我們在一個進(jìn)程內(nèi)創(chuàng)建了管道,但是尚未實(shí)現(xiàn)進(jìn)程間通信。

四. 匿名管道通信

在上文中我們提到了匿名管道通過|符號實(shí)現(xiàn)進(jìn)程間的通信,傳遞輸入給下一個進(jìn)程作為輸出,其實(shí)現(xiàn)原理如下:

  • 利用fork創(chuàng)建子進(jìn)程,復(fù)制file_struct會同樣復(fù)制fd輸入輸出數(shù)組,但是fd指向的文件僅有一份,即兩個進(jìn)程間可以通過fd數(shù)組實(shí)現(xiàn)對同一個管道文件的跨進(jìn)程讀寫操作
  • 禁用父進(jìn)程的讀,禁用子進(jìn)程的寫,即從父進(jìn)程寫入從子進(jìn)程讀出,從而實(shí)現(xiàn)了單向管道,避免了混亂
  • 對于A|B來說,shell首先創(chuàng)建子進(jìn)程A,接著創(chuàng)建子進(jìn)程B,由于二者均從shell創(chuàng)建,因此共用fd數(shù)組。shell關(guān)閉讀寫,A開寫B(tài)開讀,從而實(shí)現(xiàn)了A 和B之間的通信。

poYBAGJ_XpeAd107AADYh9naFSs930.jpg

接著我們需要調(diào)用dup2()實(shí)現(xiàn)輸入輸出和管道兩端的關(guān)聯(lián),該函數(shù)會將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 里面,有這樣一個表,下標(biāo)是 fd,內(nèi)容指向一個打開的文件 struct file。在這個表里面,前三項是定下來的,其中第零項 STDIN_FILENO 表示標(biāo)準(zhǔn)輸入,第一項 STDOUT_FILENO 表示標(biāo)準(zhǔn)輸出,第三項 STDERR_FILENO 表示錯誤輸出。

struct files_struct {
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];
}
  • 在 A 進(jìn)程寫入端通過dup2(fd[1],STDOUT_FILENO)將 STDOUT_FILENO(也即第一項)不再指向標(biāo)準(zhǔn)輸出,而是指向創(chuàng)建的管道文件,那么以后往標(biāo)準(zhǔn)輸出寫入的任何東西,都會寫入管道文件。
  • 在 B 進(jìn)程中讀取端通過dup2(fd[0],STDIN_FILENO)將 STDIN_FILENO 也即第零項不再指向標(biāo)準(zhǔn)輸入,而是指向創(chuàng)建的管道文件,那么以后從標(biāo)準(zhǔn)輸入讀取的任何東西,都來自于管道文件。

至此,我們將 A|B 的功能完成。

poYBAGJ_XpeAOG4MAABkv95ygB4483.jpg

五. 有名管道

對于有名管道,我們需要通過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)解析過了,先是通過 user_path_create() 對于這個管道文件創(chuàng)建一個 dentry,然后因?yàn)槭?S_IFIFO,所以調(diào)用 vfs_mknod()。由于這個管道文件是創(chuàng)建在一個普通文件系統(tǒng)上的,假設(shè)是在 ext4 文件上,于是 vfs_mknod 會調(diào)用 ext4_dir_inode_operations 的 mknod,也即會調(diào)用 ext4_mknod()。

在 ext4_mknod() 中,ext4_new_inode_start_handle() 會調(diào)用 __ext4_new_inode(),在 ext4 文件系統(tǒng)上真的創(chuàng)建一個文件,但是會調(diào)用 init_special_inode(),創(chuàng)建一個內(nèi)存中特殊的 inode,這個函數(shù)我們在字符設(shè)備文件中也遇到過,只不過當(dāng)時 inode 的 i_fop 指向的是 def_chr_fops,這次換成管道文件了,inode 的 i_fop 變成指向 pipefifo_fops,這一點(diǎn)和匿名管道是一樣的。這樣,管道文件就創(chuàng)建完畢了。

接下來,要打開這個管道文件,我們還是會調(diào)用文件系統(tǒng)的 open() 函數(shù)。還是沿著文件系統(tǒng)的調(diào)用方式,一路調(diào)用到 pipefifo_fops 的 open() 函數(shù),也就是 fifo_open()。在 fifo_open() 里面會創(chuàng)建 pipe_inode_info,這一點(diǎn)和匿名管道也是一樣的。這個結(jié)構(gòu)里面有個成員是 struct pipe_buffer *bufs。我們可以知道,所謂的命名管道,其實(shí)是也是內(nèi)核里面的一串緩存。接下來,對于命名管道的寫入,我們還是會調(diào)用 pipefifo_fops 的 pipe_write() 函數(shù),向 pipe_buffer 里面寫入數(shù)據(jù)。對于命名管道的讀入,我們還是會調(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é)

無論是匿名管道還是命名管道,在內(nèi)核都是一個文件。只要是文件就要有一個 inode。在這種特殊的 inode 里面,file_operations 指向管道特殊的 pipefifo_fops,這個 inode 對應(yīng)內(nèi)存里面的緩存。當(dāng)我們用文件的 open 函數(shù)打開這個管道設(shè)備文件的時候,會調(diào)用 pipefifo_fops 里面的方法創(chuàng)建 struct file 結(jié)構(gòu),他的 inode 指向特殊的 inode,也對應(yīng)內(nèi)存里面的緩存,file_operations 也指向管道特殊的 pipefifo_fops。寫入一個 pipe 就是從 struct file 結(jié)構(gòu)找到緩存寫入,讀取一個 pipe 就是從 struct file 結(jié)構(gòu)找到緩存讀出。匿名管道和命名管道區(qū)別就在于匿名管道會通過dup2()指定輸入輸出源,完成之后立即釋放,而命名管道通過mkfifo創(chuàng)建掛載后,需要手動調(diào)用pipe_read()和pipe_write()來完成其功能,表現(xiàn)到用戶端即為前面提到的例子。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11161

    瀏覽量

    208460
  • 管道
    +關(guān)注

    關(guān)注

    3

    文章

    145

    瀏覽量

    17906
  • 進(jìn)程間通信
    +關(guān)注

    關(guān)注

    0

    文章

    16

    瀏覽量

    2424
收藏 人收藏

    評論

    相關(guān)推薦

    Linux進(jìn)程如何實(shí)現(xiàn)共享內(nèi)存通信

    這次我們來講一下Linux進(jìn)程通信中重要的通信方式:共享內(nèi)存作為Linux軟件開發(fā)攻城獅,進(jìn)程
    發(fā)表于 04-26 17:14 ?661次閱讀

    Linux進(jìn)程通信方式-管道

    Linux進(jìn)程通信方式-管道分享到: 本文關(guān)鍵字: linux
    發(fā)表于 08-29 15:29

    Linux進(jìn)程通信

    華清遠(yuǎn)見嵌入式linux學(xué)習(xí)資料《Linux進(jìn)程通信》,通過前面的學(xué)習(xí),讀者已經(jīng)知道了進(jìn)程
    發(fā)表于 09-04 10:07

    Linux學(xué)習(xí)雜談】進(jìn)程通信

    我們詳細(xì)看下進(jìn)程通信大致分為以下幾個方面: Linux進(jìn)程
    發(fā)表于 10-15 14:45

    管道文件如何實(shí)現(xiàn)兩個進(jìn)程通信

    管道文件如何實(shí)現(xiàn)兩個進(jìn)程通信
    發(fā)表于 01-11 16:54

    怎樣通過匿名管道去實(shí)現(xiàn)進(jìn)程通信

    進(jìn)程通信是指什么?怎樣通過匿名管道去實(shí)現(xiàn)進(jìn)程通信呢?有哪些步驟?
    發(fā)表于 12-24 06:45

    進(jìn)程通信管道

    | grep ntp為例,描述管道通信過程,如圖8.2所示。 圖8.2 管道通信過程 管道Lin
    發(fā)表于 10-18 16:06 ?0次下載
    <b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b><b class='flag-5'>之</b>:<b class='flag-5'>管道</b>

    進(jìn)程通信Linux進(jìn)程通信概述

    人們現(xiàn)在廣泛使用的手機(jī)等方式。本章就是講述如何建立這些不同的通話方式,就像人們有多種通信方式一樣。 Linux下的進(jìn)程通信手段基本上是從UNIX平臺上的
    發(fā)表于 10-18 16:21 ?0次下載

    Linux系統(tǒng)管道和有名管道通信機(jī)制解析

    Linux 進(jìn)程通信的幾種主要手段。其中管道和有名管道是最早的
    發(fā)表于 11-07 10:51 ?0次下載

    Linux進(jìn)程通信

    linux使用的進(jìn)程通信方式:(1)管道(pipe)和有名管道(FIFO)(2)信號(sign
    發(fā)表于 04-02 14:46 ?489次閱讀

    Linux進(jìn)程通信方式——管道

    管道Linux進(jìn)程通信的一種方式,它把一個程序的輸出直接連接到另一個程序的輸入。Linux
    發(fā)表于 06-01 09:13 ?1352次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>方式——<b class='flag-5'>管道</b>

    嵌入式Linux進(jìn)程 -進(jìn)程通信

    最常用的無名管道,有名管道,消息隊列,信號,信號量,共享內(nèi)存等進(jìn)程通信方式。其實(shí)后面網(wǎng)絡(luò)通信
    發(fā)表于 11-01 17:20 ?9次下載
    嵌入式<b class='flag-5'>Linux</b><b class='flag-5'>進(jìn)程</b> -<b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>

    常見的進(jìn)程通信方式

    進(jìn)程通信 如果兩個進(jìn)程,想要知道對方在干嘛,或者進(jìn)行協(xié)調(diào)運(yùn)行,就需要進(jìn)程
    的頭像 發(fā)表于 10-08 15:48 ?1241次閱讀
    常見的<b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>方式

    進(jìn)程通信方式總結(jié)

    進(jìn)程通信(IPC): 進(jìn)程通信的方式有很多,這里主要講到
    的頭像 發(fā)表于 11-09 09:25 ?657次閱讀
    <b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>方式總結(jié)

    如何實(shí)現(xiàn)一套linux進(jìn)程通信的機(jī)制

    我們知道linux進(jìn)程通信的組件有管道,消息隊列,socket, 信號量,共享內(nèi)存等。但是我們?nèi)绻约簩?shí)現(xiàn)一套
    的頭像 發(fā)表于 11-10 14:56 ?579次閱讀
    如何實(shí)現(xiàn)一套<b class='flag-5'>linux</b><b class='flag-5'>進(jìn)程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>的機(jī)制