天天看點

鳥瞰linux的檔案讀寫全過程

http://blog.sina.com.cn/s/blog_61869e800100ek8w.html

讀寫檔案,是作為一個作業系統所提供的最基本接口之一。

我們就從寫檔案過程:open,write,close這幾個接口來說起,描述寫檔案的那些事兒。

平時,我們做應用程式的時候,常常用到讀寫檔案的函數接口,就拿寫檔案來說,我們用C/C++編寫時,用到了以下的函數接口:

1> 

FILE* fopen(const char* restrict filename,const char* restrict mode);

2>  size_t fwrite(const void* restrict buffer,size_t size,size_t n,FILE * restrict fp);

3>  int fclose(FILE * fp) ;

以上這幾個函數接口大家都比較熟悉,如果按照這個來分析似乎更加明了。然而,上面的這些接口已經是現代版本的接口,其實作依賴于現在的成熟系統,分析現行 系統的龐大代碼我還嫩了點,是以就拿過去版本的linux系統和一些原始接口進行分析吧。(其實大家都知道,現行作業系統核心的代碼量已經不是一個人一輩 子能看完的了,我們主要是借鑒linux的系統思想,去作我們自己的嵌入式作業系統)

老版本的接口是這個樣子的:

1>  int open(const char* filename,int flag,...) ;

2>  int write(int fildes,const char* buf,off_t count) ;

3>  int close(int fildes) ;

這幾個接口的聲明在頭檔案中,實作在系統的LIB庫檔案中,是以使用的時候,我們隻需要包含幾個相應的頭檔案,然後使用接口,在編譯的時候,編譯器把 LIB庫檔案中的二進制實作連結進去,這樣就行了。

當然,僅僅是使用不是本文的目的,我們是要探究的是這個使用的背後是什麼,作業系統為我們做了什麼。

首先,庫檔案中的open是怎麼實作的呢?

int open(const char * filename,int flag,...){

  register int res ;

  va_list arg ;

  va_start(arg,flag) ;

  __asm__("int $0x80"

                  :"=a"(res)     

                  :""(__NR_open),"b"(filename),"c"(flag),"d"(va_arg(arg,int))

                 );

  if(res>=0)

        return res ;

  errno = -res ;

  return -1 ;

}

庫檔案中的open函數封裝了彙編代碼“ 調用系統 ”,這個系 統調用的 傳回值 通過 eax寄存器 傳遞給了 res , 系統調用的 輸入參數 分别存放在 ebx,ecx,edx寄存器 中。

系統調用是一個中斷,是由彙編語言 int 中斷号 促發,是以好多教材上稱其為軟中斷或軟體中斷。

系統中斷中斷發生,cpu停止目前任務的處理,把使用者态的五個關鍵資訊儲存在核心态棧中,分别是:eflag,ss,esp,eip和cs寄存器,他們記 錄着程序使用者态的關鍵資訊(恢複使用者态運作時用到),把他們壓棧到核心棧中。當然,核心棧位址在程序結構資訊中早有記錄,上邊的五個寄存器的 使用者态資訊儲存與賦予核心态資訊 這個過程是由CPU自動完成的,隻要我們 在前邊的任務資料結構中設定好了就行。

任務運作在核心态中,這裡有一切系統的代碼(包括各種中斷處理程式和檔案系統以及各種裝置的驅動程式)。

呃。。。寫部落格好費時間啊,不過也是個再次詳細學習的過程,值了,畢竟能說出來才說明掌握的透徹,不像現在,邊寫邊翻資料。。。吃飯去了,回來再寫。。。

呵呵,再次拿起來這個文章,都過去一周了。接着寫,總比玩遊戲強。

依據中斷向量表的設定,程式運作到軟中斷處理程式的入口處(此時,使用者态的關鍵資訊eflag,ss,esp,eip和cs都已經儲存到核心棧中了),在 這裡(是用彙編寫的)手工壓棧儲存使用者态的其他資訊,注意,這裡的儲存,在中斷退出時,還要手工退棧恢複原:

push %ds

push %es

push %fs

pushl %eax

pushl %edx

pushl %ecx

pushl %ebx

movl $0x10 ,%edx            #0x10即 0001,0 0 00 (綠色 兩位是請求特權級,紅色 一 位是GDT(0)/LDT(1),藍色 四位是第幾項),這麼 說,edx寄存器中放的是GDT表的第0x001,0 +1項 (即第3項,0x0000是第一項),是系統資料段的選擇符 。

#把 系統資料段的 選擇符 放入ds和es寄存器中,則用到的資料都将是系統資料段(ds訓示)中的資料。

mov %dx,%ds

mov %dx,%es

movl $0x17, %edx           #0x17即 0001,0 1 11 (綠色 兩位是請求特權級,紅色 一 位是GDT(0)/LDT(1),藍色 四位是第幾項),這麼 說,edx寄存器中存放的是LDT表的第 0x001,0 +1 項(即第3項,0x0000是第一項),是使用者态資料段的選擇符 。

#把 使用者态資料段 的選擇符 放入fs 寄 存器中,則可以通過fs寄存器來實作從核心态 讀/寫使用者态 的資料(在核心中,有好多這樣的操作,諸 如:get_fs_long(),set_fs_long(),get_fs_word(),set_fs_word()等等)。

mov %dx, %fs

......

call _sys_call_table(,%eax,4)

pushl %eax       #把eax中的傳回值壓入棧

......

#中斷傳回時候,手工恢複各寄存器成使用者态時的内容

popl %eax         #儲存着系統調用的傳回值,放入eax,在使用者态的庫函數open中的傳回值就是通過這裡的eax傳遞的。

popl %ebx

popl %ecx

popl %edx

#此時,理論上應該 popl %eax 了,但是。。。

addl $4,%esp      #放棄 系統中斷調用時的 壓棧儲存的eax(上邊 紅色eax 保 存着 軟 中斷的向量号碼)

pop %fs

pop %es

pop %ds

iret                     #中斷傳回

上邊的 call _sys_call_table(,%eax,4)就是調用具體的系統調用 中斷處理函數,_sys_call_table是定義在其他檔案中定義的函數指針 數組 :

fn_ptr sys_call_table[sys_setup,sys_exit,sys_fork,sys_read,sys_write,sys_open ,......];

fn_ptr :typedef int (*fn_ptr)() ;

sys_open:extern int sys_open() ;

可以看到,sys_open在數組的第六項,這就對應了上邊在使用者态定義的 #define __NR_open  5

而 extern int sys_open對應的 實作函數在另外的檔案fs/open.c 所實作:

int sys_open(const char* filename,int flag,int mode){

     struct m_inode * inode ;

     struct file * f ;

     int i,fd ;

     for(fd=0;fd<NR_OPEN;fd++){

          if( !current->filp[fd] ) break ; 

     }

     f = 0 + file_table ;

     for(i=0;i<NR_FILE;i++,f++){

          if(!f->f_count) break ;

     }

     current->filp[fd] = f ;

     open_namei( filename,flag,mode,&inode ) ;

     f->f_mode = inode->i_mode ;

     f->f_flags = flag ;

     f->f_count = 1 ;

     f->f_inode = inode ;

     f->f_pos = 0 ;

     return (fd) ; 

}

int open_namei(const char* pathname,int flag,int mod,struct m_inode ** res_inode){

     struct m_inode * dir,* inode ;

     struct buffer_head * bh ;

     struct dir_entry * de ;

     dir = dir_namei( pathname,&namelen,&basename,NULL) ;

     bh = find_empty( &dir,basename,namelen,&de) ;

     if(!bh){

           inode = new_inode(dir->i_dev) ;

           inode->i_uid = current->euid ;

           inode->i_mode = mode ;

           inode->i_dirt = 1 ;

           bh = add_entry(dir,basename,namelen,&de) ;

           de->inode = inode->i_num ;

           de->b_dirt = 1 ;

           brelse(bh) ;

           iput(dir) ;

           * res_inode = inode ;

           return 0 ;

     }

     inr = de->inode ;

     dev = dir->i_dev ;

     brelse(bh) ;  

     inode = follow_link(dir,iget(dev,inr)) ;

     *res_inode = inode ;

     return 0 ;

}

呵呵,檔案系統是作業系統最複雜的部分,是以代碼量也最大,先寫到這裡,再有空了接着寫,總不能不工作,天天寫這個東西呀。這是學習,學習就是為了更好的 工作,不工作了哪行。下次接着寫,呵呵

繼續閱讀