转:linux文件读写

系统 1787 0

读写文件,是作为一个操作系统所提供的最基本接口之一。

我们就从写文件过程: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 ;
}

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

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

转:linux文件读写


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论