linux一切设备皆文件的实现(二)

首先struct device是设备模型中的概念,这个结构体中保存的是具体板子上设备的信息,比如基寄存器地址,寄存器范围,终端号等等,目的就是使驱动与具体的板子硬件连接剥离,使驱动更具有通用性和移植性,这样不同板子上驱动都不需要改变,struct device注册设备放在架构相关的代码中硬编码或者使用device tree。

struct cdev是一个用户接口相关的结构体,linux上一切设备皆文件,通过打开设备文件就可以操作相对应的设备,这个实现的具体过程为:
  1. 通过register_chrdev()函数注册字符设备struct cdev, struct cdev中保护kobject机构体,注册的过程就是让major 设备号与struct cdev中的kobject建立映射关系(kobj_map(cdev_map, dev,...))。
  2. 在打开设备文件时,通过存储在磁盘中的inode中的i_rdev取得设备文件对应的设备号,设备号包括major(12bit)和minor(20bit),通过major取得kobject(register_chrdev已经建立好的kobject和major映射关系),container_of()可以取得struct cdev。
  3. 填充打开的struct file的操作方法(const struct file_operations *ops)指向struct cdev中定义好的方法,这样通struct file就可以直接操作到对应的设备了。

那么struct cdev和struct device怎么对应起来呢
显然struct cdev中的方法操作的是具体的设备,所以肯定是要找到struct device的,具体怎么联系起来这个没有统一的方法,在驱动代码中可以灵活实现。
下面以spidev为例进行说明:
static int __init spidev_init(void)
{
    ...
   status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
    status = spi_register_driver(&spidev_spi_driver);
}

static const struct file_operations spidev_fops = {
    .owner =    THIS_MODULE,
    /* REVISIT switch to aio primitives, so that userspace
     * gets more complete API coverage.  It'll simplify things
     * too, except for the locking.
     */
    .write =    spidev_write,
    .read =        spidev_read,
    .unlocked_ioctl = spidev_ioctl,
    .compat_ioctl = spidev_compat_ioctl,
    .open =        spidev_open,
    .release =    spidev_release,
    .llseek =    no_llseek,
};

static struct spi_driver spidev_spi_driver = {
    .driver = {
        .name =        "spidev",
        .owner =    THIS_MODULE,
    },
    .probe =    spidev_probe,
    .remove =    __devexit_p(spidev_remove),
};

static int __devinit spidev_probe(struct spi_device *spi)          //spi_device包含struct device
{
    struct spidev_data    *spidev;
    int            status;
    unsigned long        minor;

    /* Allocate driver data */
    spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
    if (!spidev)
        return -ENOMEM;

    /* Initialize the driver data */
    spidev->spi = spi;        
    spin_lock_init(&spidev->spi_lock);
    mutex_init(&spidev->buf_lock);

    INIT_LIST_HEAD(&spidev->device_entry);

    /* If we can allocate a minor number, hook up this device.
     * Reusing minors is fine so long as udev or mdev is working.
     */
    mutex_lock(&device_list_lock);
    minor = find_first_zero_bit(minors, N_SPI_MINORS);
    if (minor < N_SPI_MINORS) {
        struct device *dev;

        spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
        dev = device_create(spidev_class, &spi->dev, spidev->devt,
                    spidev, "spidev%d.%d",
                    spi->master->bus_num, spi->chip_select);
        status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
    } else {
        dev_dbg(&spi->dev, "no minor number available!\n");
        status = -ENODEV;
    }
    if (status == 0) {
        set_bit(minor, minors);
        list_add(&spidev->device_entry, &device_list);       //放入device_list链表中
    }
    mutex_unlock(&device_list_lock);

    if (status == 0)
        spi_set_drvdata(spi, spidev);
    else
        kfree(spidev);

    return status;
}

static int spidev_open(struct inode *inode, struct file *filp)
{
    struct spidev_data    *spidev;
    int            status = -ENXIO;

    mutex_lock(&device_list_lock);

    list_for_each_entry(spidev, &device_list, device_entry) {
        if (spidev->devt == inode->i_rdev) {    //找到对应的设备
            status = 0;
            break;
        }
    }
    if (status == 0) {
        if (!spidev->buffer) {
            spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
            if (!spidev->buffer) {
                dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
                status = -ENOMEM;
            }
        }
        if (status == 0) {
            spidev->users++;
            filp->private_data = spidev;        //保存设备操作的信息
            nonseekable_open(inode, filp);
        }
    } else
        pr_debug("spidev: nothing for minor %d\n", iminor(inode));

    mutex_unlock(&device_list_lock);
    return status;
}

对于块设备, struct block_device和struct cdev的作用相似,但是这个映射过程复杂了不少,struct block_device表示一个块设备,可以是一个分区或者整个磁盘, struct gendisk表示整个磁盘:

struct block_device {
    dev_t            bd_dev;  /* not a kdev_t - it's a search key */
    int            bd_openers;
    struct inode *        bd_inode;    /* will die */
    struct super_block *    bd_super;
    struct list_head    bd_inodes;
    void *            bd_claiming;
    void *            bd_holder;
    int            bd_holders;
    bool            bd_write_holder;
    struct block_device *    bd_contains;
    unsigned        bd_block_size;
    struct hd_struct *    bd_part;
    unsigned        bd_part_count;
    int            bd_invalidated;
    struct gendisk *    bd_disk;
    struct request_queue *  bd_queue;
    struct list_head    bd_list;
    unsigned long        bd_private;
    ...
}

struct gendisk {
    int major;            /* major number of driver */
    int first_minor;
    int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

    char disk_name[DISK_NAME_LEN];    /* name of major driver */
    char *(*devnode)(struct gendisk *gd, umode_t *mode);

    struct disk_part_tbl __rcu *part_tbl;
    struct hd_struct part0;             //描述整个磁盘

    const struct block_device_operations *fops;             //操作方法
    struct request_queue *queue;
    void *private_data;
    ...
}
struct hd_struct {
    sector_t start_sect;
    sector_t nr_sects;
    sector_t alignment_offset;
    unsigned int discard_alignment;
    struct device __dev;
    struct kobject *holder_dir;
    int policy, partno;
    struct partition_meta_info *info;
    ...
}

linux一切设备皆文件的实现(二)

首先来看看块设备的注册过程:
  1. 驱动调用add_disk()来注册整个磁盘
void add_disk(struct gendisk *disk)
{
    struct backing_dev_info *bdi;
    dev_t devt;
    int retval;

    disk->flags |= GENHD_FL_UP;    
    retval = blk_alloc_devt(&disk->part0, &devt);         //分配设备号

    disk_to_dev(disk)->devt = devt;          //保存设备号

    /* ->major and ->first_minor aren't supposed to be
     * dereferenced from here on, but set them just in case.
     */
    disk->major = MAJOR(devt);
    disk->first_minor = MINOR(devt);
    ...
    blk_register_region(disk_devt(disk), disk->minors, NULL,
                exact_match, exact_lock, disk);      //通过struct hd_struct中的struct device中的kobject与设备号建立映射,这也就是说struct gendisk和设备号之间建立了映射关系
    register_disk(disk);
    blk_register_queue(disk);
    ...
}
void blk_register_region(dev_t devt, unsigned long range, struct module *module,
             struct kobject *(*probe)(dev_t, int *, void *),
             int (*lock)(dev_t, void *), void *data)
{
    kobj_map(bdev_map, devt, range, module, probe, lock, data);
}

static void register_disk(struct gendisk *disk)
{
    struct device *ddev = disk_to_dev(disk);
    struct block_device *bdev;
    struct disk_part_iter piter;
    struct hd_struct *part;
    int err;

    ddev->parent = disk->driverfs_dev;
    dev_set_name(ddev, disk->disk_name);

    if (device_add(ddev))
        return;
    ...
    /* No minors to use for partitions */
    if (!disk_part_scan_enabled(disk))     //判断是否要扫描磁盘上的分区
        goto exit;

    /* No such device (e.g., media were just removed) */
    if (!get_capacity(disk))
        goto exit;

    bdev = bdget_disk(disk, 0);       //创建整个磁盘对应的struct block_device
    if (!bdev)
        goto exit;

    bdev->bd_invalidated = 1;
    err = blkdev_get(bdev, FMODE_READ, NULL);
    if (err < 0)
        goto exit;
    blkdev_put(bdev, FMODE_READ);

exit:
    /* announce disk after possible partitions are created */
    dev_set_uevent_suppress(ddev, 0);
    kobject_uevent(&ddev->kobj, KOBJ_ADD);

    /* announce possible partitions */
    disk_part_iter_init(&piter, disk, 0);
    while ((part = disk_part_iter_next(&piter)))
        kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
    disk_part_iter_exit(&piter);
}

struct block_device *bdget_disk(struct gendisk *disk, int partno)
{
    struct hd_struct *part;
    struct block_device *bdev = NULL;

    part = disk_get_part(disk, partno);
    if (part)
        bdev = bdget(part_devt(part));   //首次调用时,创建设备号与bdev文件系统中的indoe建立映射关系,首次调用分配bdev_inode结构体,也就是说同时分配了 struct block_device, 通过inode可以得到struct block_device,这也就是说设备号和struct block_device之间也建立了映射关系
    disk_put_part(part);

    return bdev;
}

int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
{
    ...
    res = __blkdev_get(bdev, mode, 0);
    ...
}

static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
{
        ...
        disk = get_gendisk(bdev->bd_dev, &partno); //通过之前建立好的映射关系,由设备号得到struct gendisk
        bdev->bd_disk = disk;
        if (!partno) {
        ...
        rescan_partitions(disk, bdev);      //扫描分区
    }
    ...
}


struct block_device *bdget(dev_t dev)
{
    struct block_device *bdev;
    struct inode *inode;

    inode = iget5_locked(blockdev_superblock, hash(dev),
            bdev_test, bdev_set, &dev);

    if (!inode)
        return NULL;

    bdev = &BDEV_I(inode)->bdev;

    if (inode->i_state & I_NEW) {
        ...
        inode->i_mode = S_IFBLK;
        inode->i_rdev = dev;     //设备号与inode之间建立映射
        inode->i_bdev = bdev;
        ...
    }
    return bdev;
}
struct inode *iget5_locked(struct super_block *sb, unsigned long hashval,
        int (*test)(struct inode *, void *),
        int (*set)(struct inode *, void *), void *data)
{
    struct hlist_head *head = inode_hashtable + hash(sb, hashval);
    struct inode *inode;

    spin_lock(&inode_hash_lock);
    inode = find_inode(sb, head, test, data);
    spin_unlock(&inode_hash_lock);

    if (inode) {
        wait_on_inode(inode);
        return inode;
    }

    inode = alloc_inode(sb);    //首次调用时分配bdev_inode结构体
    if (inode) {
        ...
       if (set(inode, data))
        ...
    }
    return inode;

set_failed:
    spin_unlock(&inode_hash_lock);
    destroy_inode(inode);
    return NULL;
}

static int bdev_set(struct inode *inode, void *data)
{
    BDEV_I(inode)->bdev.bd_dev = *(dev_t *)data;     //保存设备号
    return 0;
}

struct bdev_inode {      
    struct block_device bdev;
    struct inode vfs_inode;
};

struct gendisk *get_gendisk(dev_t devt, int *partno)
{
    struct gendisk *disk = NULL;

    kobj = kobj_lookup(bdev_map, devt, partno);
        if (kobj)

            disk = dev_to_disk(kobj_to_dev(kobj));
}
  1. open打开设备文件, 通过存储在inode中的i_rdev在bdev文件系统中进行查找,找到inode,然后通过inode找到block_device,得到gendisk,gendisk存放着操作方法,覆盖struct file的操作方法,这样就可以操作设备了。
(kobj_map(bdev_map, devt, ...))

通过上面的分析可以看出,在设备注册过程中首先建立起设备号和设备操作方法间的映射,然后通过设备文件中的设备号得到操作方法覆盖掉普通文件操作方法,这样就可以实现设备的访问了。