读写文件,是作为一个操作系统所提供的最基本接口之一。
我们就从写文件过程: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 ;
}
呵呵,文件系统是操作系统最复杂的部分,所以代码量也最大,先写到这里,再有空了接着写,总不能不工作,天天写这个东西呀。这是学习,学习就是为了更好的工作,不工作了哪行。下次接着写,呵呵