`

鸟瞰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 ;
}

呵呵,文件系统是操作系统最复杂的部分,所以代码量也最大,先写到这里,再有空了接着写,总不能不工作,天天写这个东西呀。这是学习,学习就是为了更好的 工作,不工作了哪行。下次接着写,呵呵
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics