Chinaunix首页 | 论坛 | 博客
  • 博客访问: 573185
  • 博文数量: 117
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 359
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-13 21:58
个人简介

爱上香烟

文章分类

全部博文(117)

文章存档

2018年(3)

2017年(8)

2016年(65)

2015年(41)

我的朋友

分类: LINUX

2015-06-26 15:35:00

一.准备
1.MKDEV(ma,mi)
#define MKDEV(ma,mi)    ((ma)<<8 | (mi))
所以MKDEV(5, 0),最后得到的值为【5*(2的8次幂)+0】,等于1280。

2.dev_t

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t    dev_t;

3.MAJOR 和 MINOR

#define MAJOR(dev)    ((dev)>>8)
#define MINOR(dev)    ((dev) & 0xff)

4.major_to_index
static inline int major_to_index(int major)
{
    return major % MAX_PROBE_HASH;//MAX_PROBE_HASH=256
}

5.ERR_PTR,通过指针的方式传递错误码
linux/err.h
/*
 * Kernel pointers have redundant information, so we can use a
 * scheme where we can return either an error code or a dentry
 * pointer with the same return value.
 *
 * This should be a per-architecture thing, to allow different
 * error and pointer decisions.
 */
static inline void *ERR_PTR(long error)
{
    return (void *) error;
}

6.IS_ERR 判断错误码
static inline long IS_ERR(const void *ptr)
{
    return unlikely((unsigned long)ptr > (unsigned long)-1000L);
}

二.源代码分析
/*
 *例如:register_chrdev_region(MKDEV(5, 0), 400, "ville")
 *参数:
 *    from:
 *        所要分配设备号范围的起始值,其中的次设备号经常被设置为0。
 *    count:
 *        所请求的连续设备号的个数。如果count特别大,可能会和下一个主设备号重叠。
 *    name:
 *        和设备号关联的设备名称。
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
    struct char_device_struct *cd;
    dev_t to = from + count;/*计算分配设备号范围中的最大值,1280+400=1680*/
    dev_t n, next;

    for (n = from; n < to; n = next) {/*每次申请256个设备号*/
        next = MKDEV(MAJOR(n)+1, 0);/*主设备号加一得到的设备号,次设备号为零*/
        if (next > to)
            next = to;
        cd = __register_chrdev_region(MAJOR(n), MINOR(n),/*核心函数*/
                   next - n, name);
        if (IS_ERR(cd))
            goto fail;
    }
    return 0;
fail:/*当任何一次分配失败的时候,释放所有已经申请的设备号*/
    to = n;
    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));/*核心函数*/
    }
    return PTR_ERR(cd);
}

1.struct char_device_struct结构
fs/char_dev.c
static struct char_device_struct {
    struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
    unsigned int major; // 主设备号
    unsigned int baseminor;  // 起始次设备号
    int minorct; // 设备编号的范围大小
    const char *name; // 处理该设备编号范围内的设备驱动的名称
    struct file_operations *fops; // 没有使用
    struct cdev *cdev;        /* will die指向字符设备驱动程序描述符的指针*/
} *chrdevs[MAX_PROBE_HASH];

2.__register_chrdev_region
/*
 *例子:__register_chrdev_region(5, 0, 256, “ville”);
 *
 * Register a single major with a specified minor range.
 *
 * If major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If major > 0 this function will attempt to reserve the passed range of
 * minors and will return zero on success.
 *
 * Returns a -ve errno on failure.
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major/*5*/, unsigned int baseminor/*0*/,
               int minorct/*256*/, const char *name/*ville*/)
{
    struct char_device_struct *cd, **cp;
    int i;
    int ret = 0;

    cd = kmalloc(sizeof(struct char_device_struct), GFP_KERNEL);/*slab分配一个char_device_struct变量*/
    if (cd == NULL)
        return ERR_PTR(-ENOMEM);

    memset(cd, 0, sizeof(struct char_device_struct));/*将刚刚分配的变量的内存区清零*/

    write_lock_irq(&chrdevs_lock);/*关中断,禁止内核抢占+读写锁*/

    /* temporary */
    if (major == 0) {/*分配一个主设备号!*/
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
            if (chrdevs[i] == NULL)
                break;
        }

        if (i == 0) {
            ret = -EBUSY;
            goto out;
        }
        major = i;
        ret = major;
    }

    cd->major = major;
    cd->baseminor = baseminor;
    cd->minorct = minorct;/*申请设备号的个数*/
    cd->name = name;
    /****************以上为第一部分,处理char_device_struct变量的分配和初始化************/
    /****************以下为第二部分,将char_device_struct变量注册到内核*****************/
    i = major_to_index(major);/*将major对256取余数,得到可以存放char_device_struct在chrdevs中的索引*/

    /*
     *退出循环:
         (1)chrdevs[i]为空
         (2)chrdevs[i]的主设备号大于major
         (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor
     */
    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
        if ((*cp)->major > major ||
            ((*cp)->major == major && (*cp)->baseminor >= baseminor))
            break;
    /*
     *如果*cp不空,并且*cp的major与要申请的major相同,
     *此时,如果(*cp)->baseminor < baseminor + minorct,就会发生冲突
     *因为和已经分配了的设备号冲突了。
     *出错!
     */
    if (*cp && (*cp)->major == major &&
        (*cp)->baseminor < baseminor + minorct) {
        ret = -EBUSY;
        goto out;
    }
    
    /*
     *所要申请的设备号可以满足
     */
    cd->next = *cp;/*按照主设备号从小到达的顺序排列*/
    *cp = cd;
    write_unlock_irq(&chrdevs_lock);
    return cd;
out:
    write_unlock_irq(&chrdevs_lock);
    kfree(cd);
    return ERR_PTR(ret);
}

总结:
大体上分为两个步骤:
    1.char_device_struct类型变量的分配以及初始化
    2.将char_device_struct变量注册到内核

1.char_device_struct类型变量的分配以及初始化
    (1)首先,调用 kmalloc 分配一个 char_device_struct 变量cd。
        检查返回值,进行错误处理。
    (2)将分配的char_device_struct变量的内存区清零memset。
    (3)获取chrdevs_lock读写锁,并且关闭中断,禁止内核抢占,write_lock_irq。
    (4)如果传入的主设备号major不为0,跳转到第(7)步。
    (5)这时,major为0,首先需要分配一个合适的主设备号。
        将 i 赋值成 ARRAY_SIZE(chrdevs)-1,其中的 chrdevs 是包含有256个char_device_struct *类型的数组,
        然后递减 i 的值,直到在chrdevs数组中出现 NULL。当chrdevs数组中不存在空值的时候,
        ret = -EBUSY;    goto out;
    (6)到达这里,就表明主设备号major已经有合法的值了,接着进行char_device_struct变量的初始化。
        设置major, baseminor, minorct以及name。
2.将char_device_struct变量注册到内核
    (7)将 i 赋值成 major_to_index(major)
        将major对256取余数,得到可以存放char_device_struct在chrdevs中的索引
    (8)进入循环,在chrdevs[i]的链表中找到一个合适位置。
        退出循环的条件:
         (1)chrdevs[i]为空。
         (2)chrdevs[i]的主设备号大于major。
         (3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor。
         注意:cp = &(*cp)->next,cp是char_device_struct **类型,(*cp)->next是一个char_device_struct *
             类型,所以&(*cp)->next,就得到一个char_device_struct **,并且这时候由于是指针,所以
             对cp赋值,就相当于对链表中的元素的next字段进行操作。
    (9)进行冲突检查,因为退出循环的情况可能造成设备号冲突(产生交集)。
        如果*cp不空,并且*cp的major与要申请的major相同,此时,如果(*cp)->baseminor < baseminor + minorct,
        就会发生冲突,因为和已经分配了的设备号冲突了。出错就跳转到ret = -EBUSY; goto out;
       
    (10)到这里,内核可以满足设备号的申请,将cd链接到链表中。
    (11)释放chrdevs_lock读写锁,开中断,开内核抢占。
    (12)返回加入链表的char_device_struct变量cd。
    (13)out出错退出
        a.释放chrdevs_lock读写锁,开中断,开内核抢占。
        b.释放char_device_struct变量cd,kfree。
        c.返回错误信息   


3.__unregister_chrdev_region
static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
{
    struct char_device_struct *cd = NULL, **cp;
    int i = major_to_index(major);

    write_lock_irq(&chrdevs_lock);
    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
        if ((*cp)->major == major &&
            (*cp)->baseminor == baseminor &&
            (*cp)->minorct == minorct)
            break;
    if (*cp) {
        cd = *cp;
        *cp = cd->next;
    }
    write_unlock_irq(&chrdevs_lock);
    return cd;
}
总结:
    (1)注意只有major,baseminor和minorct全都相同,才能标示一个 char_device_struct 变量。
    (2)在__unregister_chrdev_region之中,并不释放内存。

三.总结

1.首先,内核使用char_device_struct 结构表示一次字符设备号的申请(针对__register_chrdev_region来说)。
    其中单纯对于字符设备号管理而言,最重要的就是
    struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
    unsigned int major; // 主设备号
    unsigned int baseminor;  // 起始次设备号
    int minorct; // 设备编号的范围大小
2.内核对字符设备号的集中管理体现在chrdevs数组中,它也是一个散列表,使用chrdevs_lock读写锁进行保护,
  操作的时候,需要关闭中断,关闭内核抢占。
    散列函数:major_to_index(major),单纯的进行 major%256
    使用链表处理冲突。
3.对于字符设备号申请,有可能出现,申请范围很大超出256的情况,这时候,就会在一次register_chrdev_region调用中,
    产生多个char_device_struct的情况。其中只有最后一个minorct小于等于256,其余的全都等于256。
4.alloc_chrdev_region 动态的分配设备号
char_dev.c
a.参数:
    dev:
        仅仅作为输出参数,成功分配后将保存已分配的第一个设备编号。
    baseminor:
        被请求的第一个次设备号,通常是0。
    count:
        所要分配的设备号的个数。
    name:
        和所分配的设备号范围相对应的设备名称。
b.返回值:
    成功返回0,失败返回负的错误编码。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
    *dev = MKDEV(cd->major, cd->baseminor);
    return 0;
}       
    通过以上分析很好理解。

5.unregister_chrdev_region释放字符设备号
char_dev.c
a.参数:
    from:
        所要释放的第一个设备号。
    count:
        释放设备号的个数。
b.返回值:
void unregister_chrdev_region(dev_t from, unsigned count)
{
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        if (next > to)
            next = to;
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
}
    通过对__unregister_chrdev_region的分析,很好理解这个函数。
阅读(1228) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~