从用户态的open到内核驱动实现流程

时间:2010-03-22

  作者:李强,华清远见嵌入式学院讲师。

  问题

  在讲授Linux初级驱动的时候,我发现困惑很多同学的是不真正理解从应用层到我们自己所写的驱动层的调用过程,所以写此文章来大概描述。

  首先我们知道,在我们目前的Linux系统中,我们大概共约300左右个系统调用,其中syscall_table.S列出了所有的系统调用表。

  在本文件中记录了所有当前平台系统中所提供的系统调用表,其中第五项就包括:

  .lONg sys_open /* 5 */

  -----------------------------

  查看sys_open() 函数,我们看到里面所完成的工作为:

  1、查看打开的是否是大文件,如果是的话,置大文件标志位:O_LARGEFILE

  2、做do_sys_open()函数调用。

  3、检查2的调用返回值ret是否有效。

  -----------------------------

  -----------------------------

  查看do_sys_open()函数所完成的工作为:

  调用getname() ,getname函数主要功能是在使用文件名之前将其拷贝到内核数据区,正常结束时返回内核分配的空间首地址,出错时返回错误代码。

  取得系统中可用的文件描述符fd。

  调用do_filp_open()函数,此函数使用了一个数据结构nameidata来描述与文件相关的文件操作。

  STruct nameidata {

  struct dentry                *dentry;        // 目录数据

  struct vfsmount        *mnt;        // 虚拟文件挂载点数据

  struct qstr        last;        // hash值

  unsigned int        flags;        // 文件操作标识

  int last_type;        // 类型

  unsigned                depth;

  char                        *saved_names[MAX_NESTED_LINKS + 1];

  union {

  struct open_intent open;

  } intent; // 专用数据

  };

  -----------------------------

  -----------------------------

  struct file *do_filp_open(const char * filename, int flags, int mode){

  int namei_flags, error;

  struct nameidata nd;

  namei_flags = flags;

  if ((namei_flags+1) & O_ACCMODE)

  namei_flags++;        // 如果flags有O_WRONLY,则增加O_RDONLY

  error = open_namei(filename, namei_flags, mode, &nd);

  // open_namei函数主要执行文件操作的inode部分的打开等操作。

  if (!error)

  return nameidata_to_filp (nd, flags);

  // 把文件的inod相关信息转换成文件结构。

  return ERR_PTR(error);        // 返回错误代码

  }

  -----------------------------

  -----------------------------

  我们下面来看这个比较关键的函数:nameidata_to_filp():

  struct file *(struct nameidata *nd, int flags)

  821 {

  822        struct file *filp;

  823

  824        /* Pick up the filp from the open intent */

  825        filp = nd->intent.open.file;

  // 把相关 file结构的指针赋予 filp。

  826        /* Has the filesystem initialised the file for us? */

  827        if (filp->f_path.dentry == NULL)

  828                filp = __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL);

  // ***** 关键函数 ***** //

  829        else

  830                path_release(nd);

  831        return filp;

  832 }

  -----------------------------

  -----------------------------

  关键函数:__dentry_open():

  static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,

  int flags, struct file *f,

  int (*open)(struct inode *, struct file *))

  {

  ......

  695        f->f_pos = 0;

  696        f->f_op = fops_get(inode->i_fop);

  // 在这里进行赋值,f->f_op = &def_chr_fops,注意上文inode->i_fop中的赋值。

  697        file_move(f, &inode->i_sb->s_files);

  698

  699        if (!open && f->f_op)

  // 在调用__dentry_open时open赋值为空,所以!open为真。

  700                        pen = f->f_op->open;

  // 在这里将open赋为chrdev_open。

  701        if (open) {

  702                        error = open(inode, f);

  // 这里调用chrdev_open, 参照下文。

  703                        if (error)

  704                                goto cleanup_all;

  ......

  }

  -----------------------------

  -----------------------------

  在函数chrdev_open中(/fs/char_dev.v):

  int chrdev_open(struct inode * inode, struct file * filp)

  {

  ......

  kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);

  // 执行kobj_lookup函数,在cdev_map里寻找相应的inode->i_rdev设备。

  // cdev_map是一个256个probe结构组成的数组,用于查找具有相应设备号的设备。

  // inode->i_rdev为设备号。

  new = container_of(kobj, struct cdev, kobj);

  //从kobj的位置倒算出cdev的内存地址,获得包含相应kobj的cdev。

  inode->i_cdev = p = new;

  // 到这里p已经为我们要的设备cdev了。

  filp->f_op = fops_get(p->ops);

  / /拿到 cdev操作集。

  // 至此以后read,write操作都通过file->f_op直接与我们要的设备操作集挂钩了。

  ......

  }

  -----------------------------

  到此,系统通过file->f_op 就与我们在设备驱动里面的定义的相关操作联系起来了,我们之前在写驱动实现的功能操作就被系统通过应用层的open 一步一步的调用到我们自己的open跟相关其他的操作了。

  “本文由华清远见https://www.embedu.org/index.htm提供”



  
上一篇:如何让U-boot实现Nand/Nor 双启动
下一篇:面向手机的表面波元器件

免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

相关技术资料