色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux進程間通信方法之管道

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

一. 前言

上文中我們介紹了進程間通信的方法之一:信號,本文將繼續介紹另一種進程間通信的方法,即管道。管道是Linux中使用shell經常用到的一個技術,本文將深入剖析管道的實現和運行邏輯。

二. 管道簡介

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

ps -ef | grep 關鍵字 | awk '{print $2}' | xargs kill -9

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

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

mkfifo hello

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

# echo "hello world" > hello

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

# cat < hello hello world

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

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

pYYBAGJ_XpaAG7NqAAEEq3aWrTw543.jpg

三. 匿名管道創建

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

int pipe(int fd[2])

其內核實現如下所示,pipe2 ()調用 __do_pipe_flags() 創建一個數組 files來存放管道的兩端的打開文件,另一個數組 fd 存放管道的兩端的文件描述符。如果 __do_pipe_flags() 沒有錯誤,那就調用fd_install()將兩個fd和兩個struct file關聯起來,這一點和打開一個文件的過程類似。

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()調用了create_pipe_files()生成fd,然后調用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()是管道創建的關鍵邏輯,從這里可以看出來管道實際上也是一種抽象的文件系統pipefs,有著對應的特殊文件以及inode。這里首先通過get_pipe_inode()獲取特殊inode,然后調用alloc_file_pseudo()通過inode以及對應的掛載結構體pipe_mnt,文件操作結構體pipefifo_fops創建關聯的dentry并以此創建文件結構體并分配內存,通過alloc_file_clone()創建一份新的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;
}

其虛擬文件系統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);
    }
......
}

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;
......
}

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

四. 匿名管道通信

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

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

poYBAGJ_XpeAd107AADYh9naFSs930.jpg

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

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

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

poYBAGJ_XpeAOG4MAABkv95ygB4483.jpg

五. 有名管道

對于有名管道,我們需要通過mkfifo創建,實際調用__xmknod()函數,最終調用mknod(),和字符設備創建一樣。

/* 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 在字符設備那一節已經解析過了,先是通過 user_path_create() 對于這個管道文件創建一個 dentry,然后因為是 S_IFIFO,所以調用 vfs_mknod()。由于這個管道文件是創建在一個普通文件系統上的,假設是在 ext4 文件上,于是 vfs_mknod 會調用 ext4_dir_inode_operations 的 mknod,也即會調用 ext4_mknod()。

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

接下來,要打開這個管道文件,我們還是會調用文件系統的 open() 函數。還是沿著文件系統的調用方式,一路調用到 pipefifo_fops 的 open() 函數,也就是 fifo_open()。在 fifo_open() 里面會創建 pipe_inode_info,這一點和匿名管道也是一樣的。這個結構里面有個成員是 struct pipe_buffer *bufs。我們可以知道,所謂的命名管道,其實是也是內核里面的一串緩存。接下來,對于命名管道的寫入,我們還是會調用 pipefifo_fops 的 pipe_write() 函數,向 pipe_buffer 里面寫入數據。對于命名管道的讀入,我們還是會調用 pipefifo_fops 的 pipe_read(),也就是從 pipe_buffer 里面讀取數據。

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;
......
}

總結

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

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11292

    瀏覽量

    209329
  • 管道
    +關注

    關注

    3

    文章

    145

    瀏覽量

    17962
  • 進程間通信
    +關注

    關注

    0

    文章

    16

    瀏覽量

    2434
收藏 人收藏

    評論

    相關推薦

    Linux進程如何實現共享內存通信

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

    Linux進程通信方式-管道

    Linux進程通信方式-管道分享到: 本文關鍵字: linux
    發表于 08-29 15:29

    Linux進程通信

    華清遠見嵌入式linux學習資料《Linux進程通信》,通過前面的學習,讀者已經知道了進程
    發表于 09-04 10:07

    Linux學習雜談】進程通信

    我們詳細看下進程通信大致分為以下幾個方面: Linux進程
    發表于 10-15 14:45

    管道文件如何實現兩個進程通信

    管道文件如何實現兩個進程通信
    發表于 01-11 16:54

    怎樣通過匿名管道去實現進程通信

    進程通信是指什么?怎樣通過匿名管道去實現進程通信呢?有哪些步驟?
    發表于 12-24 06:45

    進程通信管道

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

    進程通信Linux進程通信概述

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

    Linux系統管道和有名管道通信機制解析

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

    Linux進程通信

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

    Linux進程通信方式——管道

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

    嵌入式Linux進程 -進程通信

    最常用的無名管道,有名管道,消息隊列,信號,信號量,共享內存等進程通信方式。其實后面網絡通信
    發表于 11-01 17:20 ?9次下載
    嵌入式<b class='flag-5'>Linux</b><b class='flag-5'>進程</b> -<b class='flag-5'>進程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>

    常見的進程通信方式

    進程通信 如果兩個進程,想要知道對方在干嘛,或者進行協調運行,就需要進程
    的頭像 發表于 10-08 15:48 ?1339次閱讀
    常見的<b class='flag-5'>進程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>方式

    進程通信方式總結

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

    如何實現一套linux進程通信的機制

    我們知道linux進程通信的組件有管道,消息隊列,socket, 信號量,共享內存等。但是我們如果自己實現一套
    的頭像 發表于 11-10 14:56 ?631次閱讀
    如何實現一套<b class='flag-5'>linux</b><b class='flag-5'>進程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>的機制
    主站蜘蛛池模板: 久久精品AV一区二区无码| YELLOW视频直播在线观看高清| 忘忧草在线影院WWW日本动漫| 欧美日韩国产码在线| 久久青草在线视频精品| 护士喂我吃乳液我脱她内裤| 国产精品JIZZ视频免费| 成人18视频在线| 变形金刚7免费观看完整| 99久久亚洲精品日本无码| 伊人影院亚洲| 色久久久综合88一本道| 精品一品国产午夜福利视频| 征服丝袜旗袍人妻| 日韩爽爽影院在线播放| 狠狠啪在线香蕉| 3DNagoonimation动漫| 亚洲欧美日韩在线观看一区二区三区| 色欲精品国产AV久久久 | 成人久久欧美日韩一区二区三区| 1V1各种PLAY女主被肉| 中文字幕中文字幕永久免费| 亚洲午夜电影| 伊人电院网| 一品道门免费视频韩国| 亚洲色噜噜狠狠站欲八| 亚洲看片网站| 亚洲午夜无码久久久久蜜臀av| 亚洲一品AV片观看五月色婷婷| 亚洲欧美成人| 亚洲伊人久久大香线蕉综合图片| 亚洲涩福利高清在线| 亚洲字幕久久| 制服丝袜第一页| 999久久精品国产| xxxxxl荷兰| 大桥未久电影在线观看| 国产互换后人妻的疯狂VIDEO| 国产精品麻豆高潮刺激A片| 国产亚洲精品字幕在线观看| 好大的太粗好深BL|