字符设备实现原理

目录

1.    字符设备数据结构    1

2.    字符设备注册    2

3.    字符设备与文件系统关联    3

4.    设备文件的创建    4

4.1设备注册事件    4

4.2    ueventd    7

字符设备数据结构

 

struct cdev

struct kobject kobj

用于将cdev加入到kobject管理架构中去

const struct file_operations *ops

需要字符设备驱动去实现

struct list_head list

用于链接表示该字符设备的inode链表

dev_t dev

设备号包含主次设备号

unsigned int count

次设备号数

 

系统中所有cdev注册之后都会添加到kobj_map中去管理:

struct kobj_map

struct kobj_map {

struct probe {

struct probe *next; //主设备号相同的设备链表

dev_t dev; //设备号

unsigned long range; //从设备号范围

struct module *owner;

kobj_probe_t *get; //指向一个函数,用于返回设备的kobject

int (*lock)(dev_t, void *);

void *data; //指向设备的cdev结构

} *probes[255]; //major%255作为散列值

struct mutex *lock; // 访问散列表只能互斥访问

};

字符设备注册

系统中所有字符设备对应的cdev结构都是由cdev_map来管理,其组织结构如下:


void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{   
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops; //fops需要驱动实现,在文件打开的时候会替换file->f_op
}

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{   
    int error;
    
    p->dev = dev; //设备号
    p->count = count; //从设备数
 
    error = kobj_map(cdev_map, dev, count, NULL,
             exact_match, exact_lock, p); //将设备对应的cdev添加到cdev_map中
    if (error)
        return error;
 
    kobject_get(p->kobj.parent);
    
    return 0;
}

字符设备与文件系统关联

struct inode {
    umode_t         i_mode; //文件类型,例如S_IFCHR(字符设备),S_IFBLK(块设备)
    ......
    dev_t           i_rdev; //如果是设备文件表示设备号,普通文件表示文件所在存储设备的设备号
    ......
    const struct file_operations    *i_fop; //文件数据操作函数集
    union {
        struct block_device *i_bdev;//指向块设备对应的block_device
        struct cdev     *i_cdev; //指向字符设备对应的cdev
        ......
    };
    ......
};

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
    inode->i_mode = mode;
    if (S_ISCHR(mode)) { //如果是字符设备设置i_fop指向def_chr_fops
        inode->i_fop = &def_chr_fops;
        inode->i_rdev = rdev;
    } else if (S_ISBLK(mode)) {
        inode->i_fop = &def_blk_fops;
        inode->i_rdev = rdev;
    } else if (S_ISFIFO(mode))
    ......
}

通用字符设备操作函数集实现如下:

const struct file_operations def_chr_fops = {
    .open = chrdev_open,
    .llseek = noop_llseek,
};

字符设备文件打开最终会调用到这里的chrdev_open,该函数会根据设备号打开具体的设备:

static int chrdev_open(struct inode *inode, struct file *filp)
{
    const struct file_operations *fops;
    struct cdev *p;
    struct cdev *new = NULL;
    int ret = 0;
        
    spin_lock(&cdev_lock);
    p = inode->i_cdev;
    if (!p) {
        struct kobject *kobj;
        int idx;
        spin_unlock(&cdev_lock);
        kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); //根据设备号在cdev_map找到对应的cdev
        if (!kobj)
            return -ENXIO;
        new = container_of(kobj, struct cdev, kobj);
        spin_lock(&cdev_lock);
        p = inode->i_cdev;
        if (!p) {
            inode->i_cdev = p = new; //设置inode->i_cdev指向字符设备对应的cdev
            list_add(&inode->i_devices, &p->list); //将inode链接到cdev->list中去
            new = NULL;
        } else if (!cdev_get(p))
            ret = -ENXIO;
    } else if (!cdev_get(p))
        ret = -ENXIO;
	……
    fops = fops_get(p->ops); //获取cdev->ops
    if (!fops)
        goto out_cdev_put;

    replace_fops(filp, fops); //用特定字符设备的cdev->ops替换file->f_op
    if (filp->f_op->open) {
        ret = filp->f_op->open(inode, filp);//调用特定设备的open函数
        if (ret)
            goto out_cdev_put;
    }
	……
}

设备文件的创建

4.1设备注册事件

函数device_add主要做的事就是:1,创建sysfs中的目录和属性文件; 2,将device->kobj添加到kobject框架中发出kobject添加事件;3,如果dev->bus不为空就将设备添加到bus上去并且probe其对应的驱动。这里我们只关注kobject添加事件。

int device_add(struct device *dev)
{   
    struct device *parent;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;
    struct kobject *glue_dir = NULL;
    ......
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);
    
    kobject_uevent(&dev->kobj, KOBJ_ADD); //通知添加kobject
    bus_probe_device(dev);
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);
......
}

函数kobject_uevent是通知用户空间kobject相关事件,事件类型如下:

enum kobject_action {
    KOBJ_ADD,      
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,    
    KOBJ_OFFLINE,
    KOBJ_MAX    
};

其对应的字符串:

static const char *kobject_actions[] = {
    [KOBJ_ADD] =        "add",
    [KOBJ_REMOVE] =     "remove",
    [KOBJ_CHANGE] =     "change",
    [KOBJ_MOVE] =       "move",
    [KOBJ_ONLINE] =     "online",
    [KOBJ_OFFLINE] =    "offline",
};

函数kobject_uevent直接调用kobject_uevent_env来完成其具体工作:

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
               char *envp_ext[])
{
    struct kobj_uevent_env *env;
    const char *action_string = kobject_actions[action];
    const char *devpath = NULL;
    const char *subsystem;
    struct kobject *top_kobj;
    struct kset *kset;
    const struct kset_uevent_ops *uevent_ops;
    int i = 0;
    int retval = 0;
#ifdef CONFIG_NET
    struct uevent_sock *ue_sk;
#endif


    top_kobj = kobj;
    while (!top_kobj->kset && top_kobj->parent)
        top_kobj = top_kobj->parent;

    kset = top_kobj->kset;
    uevent_ops = kset->uevent_ops;

//设备所在子系统名,为dev->bus->name;或者dev->class->name;
    if (uevent_ops && uevent_ops->name)
        subsystem = uevent_ops->name(kset, kobj);
    else
        subsystem = kobject_name(&kset->kobj);

    env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
    if (!env)
        return -ENOMEM;
// 逐级回溯获取kobj->parent->name就得到最终的devpath
    devpath = kobject_get_path(kobj, GFP_KERNEL);
    if (!devpath) {
        retval = -ENOENT;
        goto exit;
}
//下面添加ACTION, DEVPATH,SUBSYSTEM到env中
    retval = add_uevent_var(env, "ACTION=%s", action_string);
    if (retval)
        goto exit;
    retval = add_uevent_var(env, "DEVPATH=%s", devpath);
    if (retval)
        goto exit;
    retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    if (retval)
        goto exit;
// uevent_ops->uevent主要添加主次设备号和设备名到env中
    if (uevent_ops && uevent_ops->uevent) {
        retval = uevent_ops->uevent(kset, kobj, env);
        if (retval) {
            pr_debug("kobject: '%s' (%p): %s: uevent() returned "
                 "%d\n", kobject_name(kobj), kobj,
                 __func__, retval);
            goto exit;
        }
    }

#if defined(CONFIG_NET)
    list_for_each_entry(ue_sk, &uevent_sock_list, list) {
        struct sock *uevent_sock = ue_sk->sk;
        struct sk_buff *skb;
        size_t len;

        if (!netlink_has_listeners(uevent_sock, 1))
            continue;

        len = strlen(action_string) + strlen(devpath) + 2;
        skb = alloc_skb(len + env->buflen, GFP_KERNEL);
        if (skb) {
            char *scratch;

            scratch = skb_put(skb, len);
            sprintf(scratch, "%s@%s", action_string, devpath);
//将前面保存到env中信息拷贝到skb中
            for (i = 0; i < env->envp_idx; i++) {
                len = strlen(env->envp[i]) + 1;
                scratch = skb_put(skb, len);
                strcpy(scratch, env->envp[i]);
            }

            NETLINK_CB(skb).dst_group = 1;
//将前面获取到的信息广播给相关应用程序
            retval = netlink_broadcast_filtered(uevent_sock, skb,
                                0, 1, GFP_KERNEL,
                                kobj_bcast_filter,
                                kobj);
            if (retval == -ENOBUFS || retval == -ESRCH)
                retval = 0;
        } else
            retval = -ENOMEM;
    }
#endif
    ......
}

4.2 ueventd

ueventd是用于监听驱动中uevent事件的一个服务,这里以android为例:

system/core/rootdir/init.rc
service ueventd /sbin/ueventd 
    class core
    critical
seclabel u:r:ueventd:s0

ueventd实现流程:

static void handle_generic_device_event(struct uevent *uevent)
{
    const char *base;
    const char *name;
    char devpath[DEVPATH_LEN] = {0};
    char **links = NULL;

    name = parse_device_name(uevent, MAX_DEV_NAME);
    if (!name)
        return;

    struct ueventd_subsystem *subsystem =
            ueventd_subsystem_find_by_name(uevent->subsystem);
//根据子系统名在/dev下面创建子系统对应的目录
    if (subsystem) {
        ......
    } else if (!strncmp(uevent->subsystem, "usb", 3)) {
        ......
     } else if(!strncmp(uevent->subsystem, "input", 5)) {
         base = "/dev/input/";
         make_dir(base, 0755);
     } else if(!strncmp(uevent->subsystem, "mtd", 3)) {
        ......
     } else
         base = "/dev/";
     links = get_character_device_symlinks(uevent);

     if (!devpath[0])
         snprintf(devpath, sizeof(devpath), "%s%s", base, name); //设备节点创建路径
//根据action,路径,主次设备号相应uevent事件
     handle_device(uevent->action, devpath, uevent->path, 0,
             uevent->major, uevent->minor, links);
}


static void handle_device(const char *action, const char *devpath,
        const char *path, int block, int major, int minor, char **links)
{
    if(!strcmp(action, "add")) { //根据设备名和主次设备号创建设备节点
        make_device(devpath, path, block, major, minor, (const char **)links);
        if (links) {
            for (int i = 0; links[i]; i++) {
                make_link_init(devpath, links[i]);
            }
        }
    }

    if(!strcmp(action, "remove")) {
        if (links) {
            for (int i = 0; links[i]; i++) {
                remove_link(devpath, links[i]);
            }
        }
        unlink(devpath);
    }

    if (links) {
        for (int i = 0; links[i]; i++) {
            free(links[i]);
        }
        free(links);
    }
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值