一、引言
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的 核心代碼的學習,開源的程式往往並非由“權威人士”、“享譽海內外的專家”所編寫,它們的由一個個普通的程式員寫就。但專業造就專家,長時間集中在某個領 域中能夠建立出據程式員應該珍視的財富。(完)