讀寫文件,是作為一個操作系統所提供的最基本接口之一。
我們就從寫文件過程: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 ;
}
呵呵,文件系統是操作系統最復雜的部分,所以代碼量也最大,先寫到這里,再有空了接著寫,總不能不工作,天天寫這個東西呀。這是學習,學習就是為了更好的工作,不工作了哪行。下次接著寫,呵呵
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
