Linux環境處理序間通訊 ——無名管道工作機制研究

來源:互聯網
上載者:User

一、引言
       Linux作為一個開源的作業系統,是我們進行作業系統和提高編程水平的最佳途徑之一。
     好的程式如同好的音樂一樣,完成的完美、巧妙。開放源碼的程式都是經過無數人檢驗地,本文將以linux-kernel-2.6.5為例對pipe的工作機制進行闡述。

二、處理序間通訊的分類
       大型程式大多會涉及到某種形式的處理序間通訊,一個較大型的應用程式設計成可以相互連信的“片段”,從而就把一個任務分到多個進程中去。處理序間通訊的方法有三種方式:
       管道(pipe)
       通訊端(socket)
       System v IPC 機制
管 道機制在UNIX開發的早期就已經提供了,它在本機上的兩個進程間的資料傳遞表現的相當出色;通訊端是在BSD(Berkeley Software Development)中出現的,現在的應用也相當的廣泛;而System V IPC機制Unix System V 版本中出現的。

三、工作機制
       管道分為pipe(無名管道)和FIFO(     具名管道),它們都是通過核心緩衝區按先進先出的方式資料轉送,管道一端順序地寫入資料,另一端順序地 讀入資料讀寫的位置都是自動增加,資料唯讀一次,之後就被釋放。在緩衝區寫滿時,則由相應的規則控制讀寫進程進入等待隊列,當空的緩衝區有寫入資料或滿的 緩衝區有資料讀出時,就喚醒等待隊列中的寫進程繼續讀寫。

管道的讀寫規則:
       管道兩端可分別用描述字fd[0]以及 fd[1]來描述,需要注意的是,管道的兩端是固定了任務的。即一端只能用於讀,由描述字fd[0]表示,稱其為管道讀端;另一端則只能用於寫,由描述字 fd[1]來表示,稱其為管道寫端。如果試圖從管道寫端讀取資料,或者向管道讀端寫入資料都將導致錯誤發生。一般檔案的I/O函數都可以用於管道,如 close、read、write等等。

四、pipe的資料結構
首先要定義一個檔案系統類型:pipe_fs_type。
fs/pipe.c
static struct file_system_type pipe_fs_type = {
     .name         = "pipefs",
     .get_sb         = pipefs_get_sb,
     .kill_sb                     = kill_anon_super,
};
       變數pipe_fs_type其類型是 struct file_system_type 用於向系統註冊檔案系統。
       Pipe 以類似檔案的方式與進程互動,但在磁碟上無對應節點,因此效率較高。Pipe主要包括一個inode和兩個file結構——分別用於讀和寫。Pipe的緩 沖區首地址就存放在inode的i_pipe域指向pipe_inode_info結構中。但是要注意pipe的inode並沒有磁碟上的映象,只在記憶體 中交換資料。

static struct super_block *pipefs_get_sb(struct file_system_type *fs_type,
     int flags, const char *dev_name, void *data)
{
     return get_sb_pseudo(fs_type, "pipe:", NULL, PIPEFS_MAGIC);
}
上為超級的產生函數。
Include/linux/pipe.h
#ifndef _LINUX_PIPE_FS_I_H
#define _LINUX_PIPE_FS_I_H

#define PIPEFS_MAGIC 0x50495045
struct pipe_inode_info {
     wait_queue_head_t wait;                               1
     char *base;                                                   2                                                        
     unsigned int len;                                           3
     unsigned int start;                                         4
     unsigned int readers;                                     5
     unsigned int writers;                                       6
     unsigned int waiting_writers;                           7
     unsigned int r_counter;                                   8
     unsigned int w_counter;                                 9
     struct fasync_struct *fasync_readers;                 10
     struct fasync_struct *fasync_writers;                 11
};
2 管道等待隊列指標wait
3 核心緩衝區基地址base
4 緩衝區當前資料量
6 管道的讀者資料量
7 管道的寫者資料量
8 等待隊列的讀者個數
9 等待隊列的寫者個數
11、12 主要對 FIFO

五、管道的建立:
通過pipe系統調用來建立管道。
int 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;

     error = -ENFILE;
     f1 = get_empty_filp();           //分配檔案對象,得到檔案對象指標用於讀管道
     if (!f1)
         goto no_files;

     f2 = get_empty_filp();           //分配檔案對象,得到檔案對象指標用於讀管道
     if (!f2)
         goto close_f1;

     inode = get_pipe_inode();     //調用get_pipe_inode獲得管道類型的索引節點
     if (!inode)                               的指標inode。
         goto close_f12;          

     error = get_unused_fd();       //獲得當前進程的兩個檔案描述符。在當前的
     if (error < 0)                           進程的進程描述符file域中,有一個fd 域,
         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);       //產生對象目錄dentry,
     this.name = name;                                   並通過它將上述兩個文
     this.len = strlen(name);                           件對象將的指標與管道
     this.hash = inode->i_ino; /* will go */         索引節點串連起來。
     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;                       f_flage設定為唯讀,f_op設定為
     f1->f_op = &read_pipe_fops;                   read_pipe_fops 結構的地址。
     f1->f_mode = 1;
     f1->f_version = 0;

     /* write file */                                   //為用於寫的兩個檔案對象設定屬性值
     f2->f_flags = O_WRONLY;                     f_flage設定為唯寫,f_op設定為
                                                                           write_pipe_fops 結構的地址。
     f2->f_op = &write_pipe_fops;
     f2->f_mode = 2;
     f2->f_version = 0;

     fd_install(i, f1);
     fd_install(j, f2);
     fd[0] = i;                                         //將兩個檔案描述符放入參數fd數組返回
     fd[1] = j;
     return 0;

close_f12_inode_i_j:
     put_unused_fd(j);
close_f12_inode_i:
     put_unused_fd(i);
close_f12_inode:
     free_page((unsigned long) PIPE_BASE(*inode));
     kfree(inode->i_pipe);
     inode->i_pipe = NULL;
     iput(inode);
close_f12:
     put_filp(f2);
close_f1:
     put_filp(f1);
no_files:
     return error;    
}

六、管道的釋放
       管道釋放時f-op的release域在讀管道和寫管道中分別指向pipe_read_release()和pipe_write_release()。而這兩個函數都調用release(),並決定是否釋放pipe的記憶體頁面或喚醒該管道等待隊列的進程。
以下為管道釋放的代碼:
static int pipe_release(struct inode *inode, int decr, int decw)
{     down(PIPE_SEM(*inode));
     PIPE_READERS(*inode) -= decr;
     PIPE_WRITERS(*inode) -= decw;
     if (!PIPE_READERS(*inode) && !PIPE_WRITERS(*inode)) {
         struct pipe_inode_info *info = inode->i_pipe;
         inode->i_pipe = NULL;
         free_page((unsigned long) info->base);
         kfree(info);
     } else {         wake_up_interruptible(PIPE_WAIT(*inode));
         kill_fasync(PIPE_FASYNC_READERS(*inode), SIGIO, POLL_IN);
         kill_fasync(PIPE_FASYNC_WRITERS(*inode), SIGIO, POLL_OUT);     }
     up(PIPE_SEM(*inode));
     return 0;}

七、管道的讀寫
1.從管道中讀取資料:
       如果管道的寫端不存在,則認為已經讀到了資料的末尾,讀函數返回的讀出位元組數為0;
當 管道的寫端存在時,如果請求的位元組數目大於PIPE_BUF,則返回管道中現有的資料位元組數,如果請求的位元組數目不大於PIPE_BUF,則返回管道中現 有資料位元組數(此時,管道中資料量小於請求的資料量);或者返回請求的位元組數(此時,管道中資料量不小於請求的資料量)。
2.向管道中寫入資料:
       向管道中寫入資料時,linux將不保證寫入的原子性,管道緩衝區一有空閑地區,寫進程就會試圖向管道寫入資料。如果讀進程不讀走管道緩衝區中的資料,那麼寫操作將一直阻塞。

八、管道的局限性
管道的主要局限性正體現在它的特點上:
?     只支援單向資料流;
?     只能用於具有親緣關係的進程之間;
?     沒有名字;
?     管道的緩衝區是有限的(管道制存在於記憶體中,在管道建立時,為緩衝區分配一個頁面大小);
?     管道所傳送的是無格式位元組流,這就要求管道的讀出方和寫入方必須事先約定好資料的格式,比如多少位元組算作一個訊息(或命令、或記錄)等等。

九、後記
       寫完本文之後,發現有部分不足之處。在由於管道讀寫的代碼過於冗長,限於篇幅不一一列出。有不足和錯誤之處還請各位老師指正。通過一段時間對Linux的 核心代碼的學習,開源的程式往往並非由“權威人士”、“享譽海內外的專家”所編寫,它們的由一個個普通的程式員寫就。但專業造就專家,長時間集中在某個領 域中能夠建立出據程式員應該珍視的財富。(完)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.