目录
字符设备数据结构
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);
}
}