/dev 目录下设备节点生成与访问过程 内核源码详解

由于我之前是在存储设备emmc上做的测试,实验/dev/mmcblock3设备节点的生成过程,这里实际讲解的是/dev/mmcblock3的生成过程。

1 mmcblock3块设备的注册

/dev/mmcblock3代表的是的块设备,在块设备驱动初始化时开始注册块设备,入口函数在drivers/mmc/card/block.c文件的mmc_blk_probe()函数,该函数主要分配与块设备紧密相关的struct  gendisk结构,并添加到系统。

  1. static int mmc_blk_probe(struct mmc_card *card)
  2. {
  3.    //分配struct gendisk 并初始化,初始化主次设备号、块设备操作函数mmc_bdops
  4.          md = mmc_blk_alloc(card);}
  5.     //添加struct gendisk *disk
  6.          if (mmc_add_disk(md))
  7.         goto out;
  8. }

 

  1. struct gendisk {
  2.          //块设备主设备号
  3.          int major;         
  4.         //第一个次设备号
  5.          int first_minor;
  6.         //该块设备的分区数
  7.          int minors;
  8.         //代表主块设备名字,mmcblk0
  9.          char disk_name[DISK_NAME_LEN];        /* name of major driver */
  10.          //disk->part_tbl_part[0]是在disk分配后赋值,在alloc_disk_node赋值,指向分区,里边的part[0]等就指向分区的struct hd_struct结构
  11.          struct disk_part_tbl __rcu *part_tbl;
  12.         //代表设备主分区
  13.          struct hd_struct part0;
  14.         //fopsalloc_disk后,在mmc_blk_alloc_req中赋值,是mmc_bdops
  15.          const struct block_device_operations *fops;
  16.         // mmc_blk_alloc_req中赋值
  17.          struct request_queue *queue;
  18. };

一个块设备,比如mmcblk0或者sda,这代表整个块设备,一般会把这个块设备分成多个分区,比如mmcblk0p1mmcblk0p2mmcblk0p3或者sda1sda2sda3。不管有几个分区,只有一个struct gendisk disk结构,但是每个分区都有自己的block_device,即bdev,主分区也有自己的bdev。继续mmc_add_disk->add_disk:

  1. void add_disk(struct gendisk *disk)
  2. {
  3.          //根据diskmajorfirst_minor,由MKDEV处理后返回devt,即主次设备号
  4.          retval = blk_alloc_devt(&disk->part0, &devt);
  5.          disk_to_dev(disk)->devt = devt;
  6.          //根据主次设备号把块设备添加到系统,有点类似字符设备的cdev_add
  7.          blk_register_region(disk_devt(disk), disk->minors, NULL, exact_match, exact_lock, disk);
  8.         //创建mmcblk0块设备主分区节点,创建主分区的bdev
  9.          register_disk(disk);
  10. }

 

  1. //创建mmcblk0块设备主分区节点,创建主分区的bdev
  2. static void  register_disk(struct gendisk *disk)
  3. {
  4.       struct device *ddev = disk_to_dev(disk);
  5.        //主分区名字mmcblk0
  6.        dev_set_name(ddev, "%s", disk->disk_name);
  7.        //添加disk->part0.dev主分区的device,之后会生成/dev下块设备节点/dev/mmcblk0
  8.        if (device_add(ddev))
  9.                    return;
  10.        //根据分区号partnodisk->part_tbl->part[partno]得到分区结构体struct hd_struct part,根据part_devt(part)块设备主次设备号,找到或者分配对应的struct block_device *bdevinodebdev_inode结构
  11.        bdev = bdget_disk(disk, 0);
  12.        //设置1将使能扫描块设备下所有分区
  13.        bdev->bd_invalidated = 1;
  14.        //调用最核心的__blkdev_get函数,open块设备bdev,对bdev大部分成员赋值,扫描块设备分区,得到bdev对应的disk,对bdev的成员bd_diskbd_queuebd_containsbd_partbd_block_size等赋值,执行diskblock open函数,执行rescan_partitions扫描块设备分区
  15.        err = blkdev_get(bdev, FMODE_READ, NULL);
  16. }
  17.  

device_add(ddev)执行后,会通知上层有新设备注册,应用层udev会获取这些信息(设备名、设备类型、主次设备号等),在/dev目录创建mmcblk0设备节点文件,这与/dev目录下手动执行命令mknod mmcblk0  b  179  0效果一样。bdev = bdget_disk(disk, 0)函数执行后,生成主分区mmcblk0的块设备struct block_device blkdev_get(bdev, FMODE_READ, NULL)执行后,会扫描该主分区下的其他分区mmcblk0p1mmcblk0p2mmcblk0p3等,然后同样执行device_add()函数通知上层在/dev/目录生成该分区的设备节点。继续看blkdev_get-> __blkdev_get-> rescan_partitions

  1. int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
  2. {
  3.          int p
  4.         struct parsed_partitions *state = NULL;
  5.         / / check_partition()获取块设备的各个分区信息:分区起始地址、分区大小等等
  6.         if (!get_capacity(disk) || !(state = check_partition(disk, bdev)))
  7.                    return 0;
  8.          //依次循环添加注册mmcblk0p1mmcblk0p2mmcblk0p3等等这些分区,之后会生成/dev/mmcblk0p1/dev/mmcblk0p2/dev/mmcblk0p3
  9.          for (p = 1; p < state->limit; p++) {
  10.               sector_t size, from;
  11.               size = state->parts[p].size;
  12.              from = state->parts[p].from;
  13.              part = add_partition(disk, p, from, size,state->parts[p].flags,&state->parts[p].info);
  14.         }
  15. }

 

  1. struct hd_struct *add_partition(struct gendisk *disk, int partno,
  2.                                      sector_t start, sector_t len, int flags,
  3.                                      struct partition_meta_info *info)
  4. {
  5.         struct hd_struct *p;
  6.         struct device *pdev;
  7.         struct device *ddev = disk_to_dev(disk);
  8.  
  9.          p = kzalloc(sizeof(*p), GFP_KERNEL);
  10.          init_part_stats(p)
  11.          pdev = part_to_dev(p);
  12.          p->start_sect = start;//分区起始地址
  13.          p->alignment_offset =
  14.                    queue_limit_alignment_offset(&disk->queue->limits, start);
  15.          p->discard_alignment =
  16.                    queue_limit_discard_alignment(&disk->queue->limits, start);
  17.          p->nr_sects = len;//分区大小
  18.          p->partno = partno;//分区号,比如mmcblk0p33,以下注释都以mmcblk0p3为例
  19.          p->policy = get_disk_ro(disk);//分区属性,是否只读
  20.         //设置分区名字
  21.          dname = dev_name(ddev);//主分区名字,如mmcblk0
  22.          if (isdigit(dname[strlen(dname) - 1]))
  23.                    dev_set_name(pdev, "%sp%d", dname, partno);//组合成设备名”mmcblk0p3”
  24.          else
  25.                    dev_set_name(pdev, "%s%d", dname, partno);
  26.  
  27.         device_initialize(pdev);
  28.          pdev->class = &block_class;
  29.          pdev->type = &part_type;
  30.          pdev->parent = ddev;
  31.         //mmcblk0p3块设备分配主次设备号
  32.          err = blk_alloc_devt(p, &devt);
  33.         //注册块设备mmcblk0p3,通知上层生成/dev/mmcblk0p3设备文件
  34.         err = device_add(pdev);
  35. }
  36.  

以上主要完成块设备的注册,通知上层mmcblk0p1mmcblk0p2mmcblk0p3这些块设备注册了,真正生成还需要udev机制。udev获取注册的这些块设备信息:设备名字、设备属性、主次设备号,然后执行mknod系统调用函数真正在/dev/目录生成设备文件节点。实际是在tmpfs文件系统下生成的这些设备文件节点,因为tmpfs文件系统挂载到了/dev目录。devtmpfs文件系统与tmpfs文件系统应该类似。

2 在/dev/目录生成设备节点的过程

当块设备驱动执行device_add注册mmcblk0p3块设备,应用层udev获取该该块设备的设备名、设备属性、主次设备号,然后执行mknod()函数,在/dev目录创建mmcblk0p3设备节点文件/dev/ mmcblk0p3,其实本质就是一个文件,下面讲解这个创建过程。系统调用mknod对应的内核函数是:

  1. SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, dev)
  2. {
  3.          return sys_mknodat(AT_FDCWD, filename, mode, dev);
  4. }

sys_mknodat()函数对应的函数是:

  1.  SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, umode_t, mode,
  2.                    unsigned, dev)
  3.  {
  4.         //filename是上层传入的文件名,mode包含设备属性,dev包含主次设备号
  5.         struct dentry *dentry;
  6.         struct path path;
  7.      
  8.        //根据/dev/mmcblk0p3搜索到父目录的dentry存于path.dentry,然后创建mmcblk0p3dentry并返回这个dentry。由于/dev是挂载点,tmpfs文件系统挂载与/dev目录,所以这是在tmpfs文件系统上创建mmcblk0p3
  9.        dentry = user_path_create(dfd, filename, &path, lookup_flags);
  10.  
  11.        switch (mode & S_IFMT) {
  12.             case 0: case S_IFREG:
  13.                    //创建一般文件,tmpfs文件系统可以创建普通文件
  14.                     error = vfs_create(path.dentry->d_inode,dentry,mode,true);
  15.                    break;
  16.                   //字符设备、块设备特殊文件的创建
  17.          case S_IFCHR: case S_IFBLK:
  18.                //path.dentry->d_inode 是父目录/dev inodedentry是刚创建的mmcblk0p3文件的。由于/dev是挂载点,属于tmpfs文件系统,所以这是tmpfs文件系统上创建mmcblk0p3
  19.                error = vfs_mknod(path.dentry->d_inode,dentry,mode, new_decode_dev(dev));
  20.                break;
  21.         case S_IFIFO: case S_IFSOCK:
  22.                 error = vfs_mknod(path.dentry->d_inode,dentry,mode,0);
  23.                 break;
  24.          }
  25. }

user_path_create()函数执行流程是user_path_create-> kern_path_create-> do_path_lookup-> filename_lookup-> path_lookupatpath_lookupat()函数前文介绍过,就是open一个文件的核心函数。这里的操作是,open “/dev/mmcblk0p3”,获取/dev目录的dentry,然后创建mmcblk0p3文件dentry。需要注意的是,本次创建的是块设备这种特殊文件,并不是普通的文件,除了有文件名字mmcblk0p3即设备名外,还有属性(S_IFBLK)、主次设备号。vfs_mknod()函数中执行dir->i_op->mknod(),证实是shmem_mknod()

  1. static int shmem_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
  2. {
  3.           //创建/dev/mmcblk0p3文件inodedir是父目录的inodedentry/dev/mmcblk0p3mmcblk0p3文件的,dev是主次设备号
  4.          inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);
  5.          //建立inodedentry的联系
  6.          d_instantiate(dentry, inode);
  7. }

 

  1. static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir,
  2.                                           umode_t mode, dev_t dev, unsigned long flags)
  3. {
  4.             struct inode *inode;
  5.             struct shmem_inode_info *info;
  6.            //tmpfs文件系统的超级块
  7.            struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
  8.           //创建/dev/mmcblk0p3文件的inode
  9.            inode = new_inode(sb);
  10.            if (inode) {
  11.                    //获取inode编号
  12.                    inode->i_ino = get_next_ino();
  13.                    switch (mode & S_IFMT) {
  14.                          default:
  15.                             //特殊文件,设备节点、管道、sock设备走这个分支
  16.                             //比如此时mmcblk0p3设备节点文件的创建
  17.                             inode->i_op = &shmem_special_inode_operations;
  18.                             init_special_inode(inode, mode, dev);
  19.                             break;
  20.                    case S_IFREG://tmpfs文件系统 普通文件
  21.                             inode->i_mapping->a_ops = &shmem_aops;
  22.                             inode->i_op = &shmem_inode_operations;
  23.                             inode->i_fop = &shmem_file_operations;
  24.                             mpol_shared_policy_init(&info->policy,
  25.                                                          shmem_get_sbmpol(sbinfo));
  26.                             break;
  27.                    case S_IFDIR://tmpfs文件系统 目录
  28.                             inc_nlink(inode);
  29.                             /* Some things misbehave if size == 0 on a directory */
  30.                             inode->i_size = 2 * BOGO_DIRENT_SIZE;
  31.                             inode->i_op = &shmem_dir_inode_operations;
  32.                             inode->i_fop = &simple_dir_operations;
  33.                             break;
  34.                 }
  35.           }
  36. }
  37.  

shmem_get_inode()函数主要是创建/dev/mmcblk0p3文件的inode,然后对inode进行初始化。文件的inode才真正与磁盘对应起来,包含文件的基本数据,比如修改时间、物理记录块号等元数据。不过本次情况有点特殊,是tmpfs文件系统挂载到/dev目录,是在tmpfs文件系统创建文件,tmpfs文件系统是内存型文件系统,并没有磁盘对应,所以创建inode的过程非常简单,没有对物理磁盘有什么读写操作。接着看init_special_inode()函数。

  1. void  init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
  2. {
  3.          inode->i_mode = mode;
  4.          if (S_ISCHR(mode)) {//字符设备
  5.                    inode->i_fop = &def_chr_fops;//字符设备文件操作操作结构体
  6.                    inode->i_rdev = rdev; //字符设备的主次设备号
  7.          } else if (S_ISBLK(mode)) {//块设备
  8.                    inode->i_fop = &def_blk_fops; //块设备设备文件操作操作结构体
  9.                    inode->i_rdev = rdev; //块设备的主次设备号
  10.          } else if (S_ISFIFO(mode))
  11.                    inode->i_fop = &pipefifo_fops;
  12.          else if (S_ISSOCK(mode))
  13.                    inode->i_fop = &bad_sock_fops;
  14.          ……..
  15. }

init_special_inode()函数中,完成对inode->i_fop = &def_blk_fops块设备文件操作函数的赋值,inode->i_rdev = rdev 是对inode主次设备号的赋值,将来在open “/dev/mmcblkop3”时用到。最后再啰嗦一点,等/dev/mmcblk0p3设备文件节点创建后,可以直接读写该块设备,比如cat  /dev/mmcblk0p3。第一步肯定是open /dev/mmcblk0p3,这时走的流程与open一个普通文件是相同的,内核执行流程是sys_open-> do_sys_open-> do_filp_open-> path_openat这里重点介绍的是open的最后一步,path_openat-> do_last-> finish_open-> do_dentry_open

  1. // struct file *f/dev/mmcblk0p3 文件的struct file结构
  2. static int do_dentry_open(struct file *f,
  3.                               int (*open)(struct inode *, struct file *),
  4.                               const struct cred *cred)
  5. {
  6.          struct inode *inode;
  7.          // 这是/dev/mmcblk0p3 文件的inode结构
  8.          inode = f->f_inode = f->f_path.dentry->d_inode;
  9.           f->f_mapping = inode->i_mapping;
  10.          //返回inode->i_fop,就是前文init_special_inode ()中赋值的def_blk_fops 结构
  11.          f->f_op = fops_get(inode->i_fop);
  12.         open = f->f_op->open;
  13.        //执行blkdev_open()函数
  14.        error = open(inode, f);
  15.       return 0;
  16. }

 

  1. static int  blkdev_open(struct inode * inode, struct file * filp)
  2.         //这个inode/dev/mmcblk0p3的,inode->i_rdevmmcblk0p3块设备的主次设备号,根据这个主次设备号,找到或者创建基于mmcblk0p3块设备的bdev
  3.        bdev = bd_acquire(inode);
  4.        //块设备struct address_space 页高速缓存,读写块设备用到
  5.         filp->f_mapping = bdev->bd_inode->i_mapping;
  6.         //完成底层mmcblk0p3块设备的open
  7.         return blkdev_get(bdev, filp->f_mode, filp);
  8. }

bd_acquire()在前文介绍mount内核过程详细介绍过,它在这里的作用是根据/dev/mmcblk0p3文件的主次设备号inode->i_rdev,找到或者创建该块设备的struct block_device结构,之后才可以操作这个块设备硬件。

你可能感兴趣的:(linux,内核)