版权@smilestone322,转载注明出处!谢谢


2.4  linux驱动开发基础

 

linux 驱动包括3 个方面的内容,字符驱动,块驱动,网络驱动,这3 种驱动有不同之处,但是驱动模型都是类似的,在讲解linux 驱动开发基础时,先以字符驱动为例,讲解下字符驱动的驱动模型,然后也简单的介绍块设备驱动,和网络设备驱动。

2.4.1 字符设备驱动

在linux 系统中,很多驱动是字符型驱动,有些之间编译在内核中,有些以.ko 文件动态加载。其实对于所有的驱动,都是类似的,用于内核与应用程序之间的通信,都是调用open ,release, read ,write, ioctl 等例程。前面我们将windows 驱动开发基础时,也讲到了windows 应用程序和驱动程序之间通信的函数。和linux 是类似的,windows 提供的api 有OpenFile ,CloseHandle ,ReadFile ,WriteFile ,DeviceIoControl 等,windows 下这些应用层的api 是怎样和驱动里面的函数对应起来的呢,比如说,应用层调用ReadFile ,驱动怎么知道调用对应的Read 函数呢,这是通过io 管理器操作的,应用程序调用ReadFile 时,通过IRP 设置派遣函数,就可以调用到驱动中的函数了,如果你感兴趣,可以看看我写的《庖丁解牛—winpcap 源码彻底解密》,只看驱动的部分,就会明白了,那么linux 这些函数,是怎么和驱动里面的函数对应起来的呢,大家都知道 linux 的字符驱动一般都会在/dev/ 目录下建一个设备节点,然后通过对设备节点进行操作,这个操作就类似文件操作,那么这里面的原理是什么呢?很多人想搞清楚llinux 内核在这个操作中,起到了什么作用,但是大多数的linux 驱动开发者可能都没有搞明白怎么回事,只是知道 通过文件结构file_operations 然后通过以下函数注册,比如在 sculll 例子中,通过文件结构 将read 和scull_read 函数联系以来了,但是具体是怎么联系起来的呢,内核起到了怎样的作用,大多数的驱动开发者都是一只半解。­

 

  1. struct file_operations scull_fops = {

  2. .owner = THIS_MODULE,

  3. .llseek = scull_llseek,

  4. .read = scull_read,

  5. .write = scull_write,

  6. .ioctl = scull_ioctl,

  7. .open = scull_open,

  8. .release = scull_release,

};

 

cdev_init(&dev->cdev, &scull_fops);

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &scull_fops;

err = cdev_add (&dev->cdev, devno, 1);

 

首先看看file_operations 这个结构体吧,在<include/linux/fs.h> 文件中,源码如下:

/*

* NOTE:

* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl

* can be called without the big kernel lock held in all filesystems.

*/

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

};

scull_fops ,只是填充了 file_operations文件结构中的一部分函数, 还是以LDD3 里面的字符驱动的例子进行讲解吧,大多数的朋友都是看着这本书开始自己的linux 的驱动生涯的,下面先将LDD3 里面字符驱动程序的一段源码贴上来。这段代码虽然没有对硬件进行操作,但是麻雀虽小,五脏俱全,拿来讲解本节的内容,恰到好处。

还有2 个结构体也比较重要,struct file; struct inode; 下面先把这两个结构源码贴上:

 

struct file {

/*

* fu_list becomes invalid after file_free is called and queued via

* fu_rcuhead for RCU freeing

*/

union {

struct list_head fu_list;

struct rcu_head fu_rcuhead;

} f_u;

struct path f_path;

#define f_dentry f_path.dentry

#define f_vfsmnt f_path.mnt

const struct file_operations *f_op; /* 指向文件操作的指针 */

spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */

atomic_long_t f_count;

unsigned int f_flags; /* 文件标志,如 O_RDONLY O_NONBLOCK O_SYNC,其中 O_NONBLOCK 用来检查用户的请求是否是非阻塞的操作 */

fmode_t f_mode; /* 文件模式。通过 FMODE_READ FMODE_READ判断是否可读可写 */

loff_t f_pos; /* 当前的读写位置 */

struct fown_struct f_owner;

const struct cred *f_cred;

struct file_ra_state f_ra;

 

u64 f_version;

#ifdef CONFIG_SECURITY

void *f_security;

#endif

/* needed for tty driver, and maybe others */

void *private_data; /* */

 

#ifdef CONFIG_EPOLL

/* Used by fs/eventpoll.c to link all the hooks to this file */

struct list_head f_ep_links;

#endif /* #ifdef CONFIG_EPOLL */

struct address_space *f_mapping;

#ifdef CONFIG_DEBUG_WRITECOUNT

unsigned long f_mnt_write_state;

#endif

};

 

 

struct inode {

struct hlist_node i_hash;

struct list_head i_list;/* backing dev IO list */

struct list_head i_sb_list;

struct list_head i_dentry;

unsigned long i_ino;

atomic_t i_count;

unsigned int i_nlink;

uid_t i_uid;

gid_t i_gid;

dev_t i_rdev; //真的设备编号

unsigned int i_blkbits;

u64 i_version;

loff_t i_size;

#ifdef __NEED_I_SIZE_ORDERED

seqcount_t i_size_seqcount;

#endif

struct timespec i_atime;

struct timespec i_mtime;

struct timespec i_ctime;

blkcnt_t i_blocks;

unsigned short i_bytes;

umode_t i_mode;

spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */

struct mutex i_mutex;

struct rw_semaphore i_alloc_sem;

const struct inode_operations *i_op;

const struct file_operations *i_fop;/* former ->i_op->default_file_ops */

struct super_block *i_sb;

struct file_lock *i_flock;

struct address_space *i_mapping;

struct address_space i_data;

#ifdef CONFIG_QUOTA

struct dquot *i_dquot[MAXQUOTAS];

#endif

struct list_head i_devices;

union {

struct pipe_inode_info *i_pipe;

struct block_device *i_bdev;

struct cdev *i_cdev; /* 字符设备的内核的内部结构 */

};

__u32 i_generation;

 

#ifdef CONFIG_FSNOTIFY

__u32 i_fsnotify_mask; /* all events this inode cares about */

struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */

#endif

 

#ifdef CONFIG_INOTIFY

struct list_head inotify_watches;/* watches on this inode */

struct mutex inotify_mutex;/* protects the watches list */

#endif

 

unsigned long i_state;

unsigned long dirtied_when;/* jiffies of first dirtying */

unsigned int i_flags;

atomic_t i_writecount;

#ifdef CONFIG_SECURITY

void *i_security;

#endif

#ifdef CONFIG_FS_POSIX_ACL

struct posix_acl *i_acl;

struct posix_acl *i_default_acl;

#endif

void *i_private; /* fs or device private pointer */

};

 

这几个结构体都在<linux/fs.h> 中定义;

 

int scull_open(struct inode *inode, struct file *filp)

{

struct scull_dev *dev; /* device information */

dev = container_of(inode->i_cdev, struct scull_dev, cdev);

filp->private_data = dev;/* for other methods */

/* now trim to 0 the length of the device if open was write-only */

if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {

if (down_interruptible(&dev->sem))

return -ERESTARTSYS;

scull_trim (dev);/* ignore errors */

up (&dev->sem);

}

return 0; /* success */

}

LDD3 中的scull_open 函数由于没有对任何硬件进行操作,所以就没有什么可以分析的了,其实如果要对硬件进行操作,在最下面一层就会调用chrdev_open 函数。下面讲讲文件系统对设备文件的访问,对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev 为 NULL , 则说明该设备文件没有被打开。由于多个设备可以共用同一个驱动程序. 所以, 通过字符设备的inode 中的i_devices 和 cdev 中的list 组成一个链表.

 

 

系统调用open 打开一个字符设备的时候, 通过一系列调用, 最终会执行到 chrdev_open.

int chrdev_open(struct inode * inode, struct file * filp)
源码如下:

/*

* Called every time a character special file is opened

*/

static int chrdev_open(struct inode *inode, struct file *filp)

{

struct cdev *p;

struct cdev *new = NULL;

int ret = 0;

spin_lock(&cdev_lock);

p = inode->i_cdev; //获取字符设备

if (!p) { //p!=NULL ,说明设备文件已经被打开

struct kobject *kobj;

int idx;

spin_unlock(&cdev_lock);

kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);

/*

在字符设备驱动模型中查找对应的驱动程序 ,这通过 kobj_lookup() 来实现 , kobj_lookup() 会返回对应驱动程序 cdev kobject

*/

if (!kobj)

return -ENXIO;

new = container_of(kobj, struct cdev, kobj);

spin_lock(&cdev_lock);

/* Check i_cdev again in case somebody beat us to it while

we dropped the 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;

spin_unlock(&cdev_lock);

cdev_put(new); //new 引用减 1

if (ret)

return ret;

 

ret = -ENXIO;

filp->f_op = fops_get(p->ops); // 使用 cdev ops设置 file 对象的 f_op

if (!filp->f_op)

goto out_cdev_put;

if (filp->f_op->open) {

ret = filp->f_op->open(inode,filp);/* 如果 ops中定义了 open 方法 ,则调用该 open 方法。调用的我们字符驱动中定义的 .open函数*/

if (ret)

goto out_cdev_put;

}

return 0;

out_cdev_put:

cdev_put(p);

return ret;

}

 

执行完chrdev_open() 之后,file 对象的f_op 指向cdev 的ops ,因而之后对设备进行的read, write 等操作,就会执行cdev 的相应操作。当应用程序执行open 操作时,最终会调用chardev_open 函数,其实这个函数是在chardev.c 文件中定义了一个文件操作,如下:

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

在linux 的文件系统中会调用def_chr_fops ,从而就可以调用chrdev_open 了,在这里为什么不定义其它的文件操作呢,像一般的驱动,都定了read 和write 函数? 在def_chr_fops 中为什么不定义read 和write 函数,从chrdev_open 的源码中可以看出,因为在它的源码中有filp->f_op=fops_get(p->ops) ;这样调用把文件和设备操作就联系起来了。即当应用程序通过 open 函数,最终调用的是 chrdev_open 函数,然后在 chrdev_open函数中调用 scull_fops 中定义的 scull_open函数;

struct file_operations scull_fops = {

.owner = THIS_MODULE,

.llseek = scull_llseek,

.read = scull_read,

.write = scull_write,

.ioctl = scull_ioctl,

.open = scull_open,

.release = scull_release,

};

获取设备号后,初始化设备,然后应用程序就可以通过open 函数打开设备,对应到了scull_open ,同样的当应用程序使用read 函数时,驱动就对应了scull_read ,当应用程序使用write 函数时,驱动就对应了scull_write 函数;

/*

* The cleanup function is used to handle initialization failures as well.

* Thefore, it must be careful to work correctly even if some of the items

* have not been initialized

*/

void scull_cleanup_module(void)

 

{

int i;

dev_t devno = MKDEV(scull_major, scull_minor);

/* Get rid of our char dev entries */

if (scull_devices) {

for (i = 0; i < scull_nr_devs; i++) {

scull_trim(scull_devices + i);

cdev_del(&scull_devices[i].cdev);

}

kfree(scull_devices);

}

#ifdef SCULL_DEBUG /* use proc only if debugging */

scull_remove_proc();

#endif

 

/* cleanup_module is never called if registering failed */

unregister_chrdev_region(devno, scull_nr_devs);

/* and call the cleanup functions for friend devices */

scull_p_cleanup();

scull_access_cleanup();

}

/*

* Set up the char_dev structure for this device.

*/

 

static void scull_setup_cdev(struct scull_dev *dev, int index)

{

int err, devno = MKDEV(scull_major, scull_minor + index);

// 生成设备编号

cdev_init(&dev->cdev, &scull_fops );

/*

要搞清楚 cdev_init的函数的作用,首先必须知道 cdev 的数据结构, cdev 的数据结构在 cdev.h中,定义如下:

struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

如上所见, cdev结构中嵌套了 kobject 结构,如果使用该结构,只需要访问 kobject 成员就能获得 kobject 对象。如果要通过一个给定的 kobject指针,如何获得包含它的 cdev 结构指针的呢?采用 container_of 宏解决这个问题:

Struct cdev *device=container_of(kp,struct cdev,kobj);

其中kp 为kobject 的结构体指针,第2 个参数为要获取的结构体指针的数据类型,第3 个参数为kobject 的对象;

Cdev 这个结构相当重要,首先看看 kobject 结构,这就要开始讲解设备模型中的 3个重要的数据结构了,首先看看 struct kobject这个结构吧,在 <linux/kobject.h>中定义如下:

struct kobject {

const char *name;

struct list_headentry;

struct kobject *parent;

struct kset *kset; //kset

struct kobj_type *ktype; //ktype

struct sysfs_dirent *sd;

struct krefkref ;

unsigned int state_initialized:1;

unsigned int state_in_sysfs:1;

unsigned int state_add_uevent_sent :1;

unsigned int state_remove_uevent_se nt:1;

unsigned int uevent_suppress:1;

}

 

另外2 个重要的数据结构就是struct kset 和struct kobi_type 了,同样在<linux/ktype.h> 中找到它的定义如下:

struct kset {

struct list_head list;

spinlock_t list_lock;

struct kobjectkobj;

const struct kset_uevent_ops *uevent_ops;

};

 

struct kobj_type {

void (*release)(struct kobject *kobj);

const struct sysfs_ops *sysfs_ops;

struct attribute **default_attrs;

const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);

const void *(*namespace)(struct kobject *kobj);

};

将这3 个数据结构罗列出来了,就可以开始讲解cdev_init 函数了。

Cdev_init char_dev.c 中定义, <fs/char_dev.c>找了好久才找到这个函数。

/**

* cdev_init() - initialize a cdev structure

* @cdev: the structure to initialize

* @fops: the file_operations for this device

*

* Initializes @cdev, remembering @fops, making it ready to add to the

* system with cdev_add().

*/

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;

}

首先看我们的实参,传递进来的是&dev->cdev, &scull_fops ,有必要跟踪到 kobject_init 函数中去看看, kobject_init在哪个函数中定义呢,真麻烦,搜了一圈,发现在 /lib/ kobject.c 中找到了它的源码;源码如下:

//kobject_init函数的作用是初始化 kobj, 主要调用 kobject_init_internal kobj ,该函数在 kobject.c 中定义 (/lib/)

voidkobject_init (struct kobject *kobj, struct kobj_type *ktype)

{

char *err_str;

if (!kobj) {

err_str = "invalid kobject pointer!";

goto error;

}

if (!ktype) {

err_str = "must have a ktype to be initialized properly!/n";

goto error;

}

if (kobj->state_initialized) {

/* do not error out as sometimes we can recover */

printk(KERN_ERR "kobject (%p): tried to init an initialized "

"object, something is seriously wrong./n", kobj);

dump_stack();

}

kobject_init_internal(kobj);

kobj->ktype = ktype;

return;

 

error:

printk(KERN_ERR "kobject (%p): %s/n", kobj, err_str);

dump_stack();

}

EXPORT_SYMBOL(kobject_init);

这个函数又调用了 kobject_init_internal kobj ),没办法,在跟踪进去,看这个函数的作用是什么。这个函数的源码也在 kobject.c中,这个就是对 kobj 的成员初始化了,注意 kobj->state_initialized = 1;这个的作用是告诉程序已经初始化了,如果再次调用上面的初始化函数,就会报错了,在 kobject_init中有个 if 语句控制。 kobject_init_internal函数中的 kref_init 函数的作用是 void kref_init(struct kref *kref){        atomic_set(&kref->refcount, 1);}将引用计数置为 1

static void kobject_init_internal(struct kobject *kobj)

{

if (!kobj)

return;

kref_init(&kobj->kref);

INIT_LIST_HEAD(&kobj->entry);

kobj->state_in_sysfs = 0;

kobj->state_add_uevent_sent = 0;

kobj->state_remove_uevent_sent = 0;

kobj->state_initialized = 1;

}

EXPORT_SYMBOL(kobject_init);

 

*/

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &scull_fops;

/*cdev_add 函数作用:初始化 cdev 后,需要把它添加到系统 中去。为此可以调用 cdev_add()函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。*/

err = cdev_add (&dev->cdev, devno, 1);

/* 分析到这里,还是按照惯例去跟踪下源码吧

int cdev_add(struct cdev *p, dev_t dev, unsigned count) //count=1

{

p->dev = dev;

p->count = count;

return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

}

其中 char_dev.c 中定义下面结构:

static struct kobj_map *cdev_map;kobj_map函数的作用: 内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map 变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用 cdev_del() 函数来释放 cdev 占用的内存。下面看看 kobj_map 函数是怎样实现这个功能的,源码如下:

下面看看kobj_map 源码,在drivers/base/map.c 中找到了这个函数:

struct kobj_map {

struct probe {

struct probe *next;

dev_t dev;

unsigned long range;

struct module *owner;

kobj_probe_t *get;

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

void *data;

} *probes[255];

struct mutex *lock;

};

 

p { margin-bottom: 0.21cm; }a:link { color: rgb(0, 102, 255); }

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

struct module *module, kobj_probe_t *probe,

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

{

unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;//range=1,n=1;

unsigned index = MAJOR(dev);// 第一个主设备号

unsigned i;

struct probe *p;

if (n > 255)

n = 255;

p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);// 分配内存

if (p == NULL)

return -ENOMEM;

for (i = 0; i < n; i++, p++) {

p->owner = module;

p->get = probe;

p->lock = lock;

p->dev = dev;

p->range = range;

p->data =data;

}

/* n probe 结构赋初值,从这里看没有给 p->next赋初值,将 p->data 指向 cdev*/

mutex_lock(domain->lock);// 对下面代码进行加锁

for (i = 0, p -= n; i < n; i++, p++, index++) {//n=1

struct probe **s = &domain->probes[index % 255];// 找到对应设备 probes

while (*s && (*s)->range < range)

s = &(*s)->next;

p->next = *s;

*s = p;

}

/* 这部分代码的作用是将 p 插入到 domain对应的 probes 中,根据 index进行索引,这样刚才说的没有为 p->next赋初值的问题在这里就赋值了 */

mutex_unlock(domain->lock);

return 0;

}*/

/* Fail gracefully if need be */

if (err)

printk(KERN_NOTICE "Error %d adding scull%d", err, index);

}

 

/*   kobj_map()函数的作用: kobj_map() 会创建一个 probe对象,然后将其插入 cdev_map 中的某一项中,并关联 probe->data 指向 cdev*/

 

/*

无论什么时候,分析驱动,都是先看初始化函数,在这段代码中,初始化函数就是 scull_init_module

*/

int scull_init_module(void)

{

int result, i;

dev_t dev = 0;

/*

* Get a range of minor numbers to work with, asking for a dynamic

* major unless directed otherwise at load time.

*/

if (scull_major) {

dev = MKDEV(scull_major, scull_minor); // 通过主设备号,次设备号生成设备编号

result = register_chrdev_region(dev, scull_nr_devs, "scull");//#char_dev.c

/*

/**

* register_chrdev_region() - register a range of device numbers

* @from: the first in the desired range of device numbers; must include

* the major number.

* @count: the number of consecutive device numbers required

* @name: the name of the device or driver.

*

* Return value is zero on success, a negative error code on failure.

*/

int register_chrdev_region(dev_t from, unsigned count, const char *name)

{

struct char_device_struct *cd;

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;

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);

}

*/

} else {

result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,

"scull");

scull_major = MAJOR(dev);

}

/* 上面的代码就是为设备分配主设备号, LDD3建议动态的获取主设备号,即推荐使用 alloc_chrdev_region函数还是不是采用 register_chrdev_region函数;下面到 fs.h 中跟踪下这个函数的源码; alloc_chrdev_region的源码如下:

/**

* alloc_chrdev_region() - register a range of char device numbers

* @dev: output parameter for first assigned number

* @baseminor: first of the requested range of minor numbers

* @count: the number of minor numbers required

* @name: the name of the associated device or driver

*

* Allocates a range of char device numbers. The major number will be

* chosen dynamically, and returned (along with the first minor number)

* in @dev. Returns zero or a negative error code.

*/

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;

}

从上面的代码中可以看出,它是调用_register_chrdev_region 函数完成操作的,第一个参数为0 即为动态分配主设备号。如果不为0 ,那就不是动态分配主设备号了,可以参考register_chrdev_region 函数,下面是_register_chrdev_region 的源码:

/*

* 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, unsigned int baseminor,

int minorct, const char *name)

{

struct char_device_struct *cd, **cp;

int ret = 0;

int i;

cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

if (cd == NULL)

return ERR_PTR(-ENOMEM);

mutex_lock(&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;

strlcpy(cd->name, name, sizeof(cd->name));

 

i = major_to_index(major);

 

for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

if ((*cp)->major > major ||

((*cp)->major == major &&

(((*cp)->baseminor >= baseminor) ||

((*cp)->baseminor + (*cp)->minorct > baseminor))))

break;

 

/* Check for overlapping minor ranges. */

if (*cp && (*cp)->major == major) {

int old_min = (*cp)->baseminor;

int old_max = (*cp)->baseminor + (*cp)->minorct - 1;

int new_min = baseminor;

int new_max = baseminor + minorct - 1;

 

/* New driver overlaps from the left. */

if (new_max >= old_min && new_max <= old_max) {

ret = -EBUSY;

goto out;

}

 

/* New driver overlaps from the right. */

if (new_min <= old_max && new_min >= old_min) {

ret = -EBUSY;

goto out;

}

}

 

cd->next = *cp;

*cp = cd;

mutex_unlock(&chrdevs_lock);

return cd;

out:

mutex_unlock(&chrdevs_lock);

kfree(cd);

return ERR_PTR(ret);

}

*/

if (result < 0) {

printk(KERN_WARNING "scull: can't get major %d/n", scull_major);

return result;

}

/*

* allocate the devices -- we can't have them static, as the number

* can be specified at load time

*/

 

scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);

 

if (!scull_devices) {

result = -ENOMEM;

goto fail; /* Make this more graceful */

}

memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

/* Initialize each device. */

for (i = 0; i < scull_nr_devs; i++) {

scull_devices[i].quantum = scull_quantum;

scull_devices[i].qset = scull_qset;

init_MUTEX(&scull_devices[i].sem);

scull_setup_cdev (&scull_devices[i], i);

}

/* At this point call the init function for any friend device */

dev = MKDEV(scull_major, scull_minor + scull_nr_devs);

dev += scull_p_init(dev);

dev += scull_access_init(dev);

#ifdef SCULL_DEBUG /* only when debugging */

scull_create_proc();

#endif

return 0; /* succeed */

fail:

scull_cleanup_module();

return result;

}

module_init(scull_init_module);

module_exit(scull_cleanup_module);

 

2.4.2 linux 设备模型的主角

2.4.1 节中讲了字符设备驱动,从里面我们也看到了字符设备驱动模型,各种复杂的数据结构,比如说 kset, kobject, ktype f_ops file inode 结构,也熟悉了字符设备驱动从获取设备编号,初始化, open 函数,然后到 read write 的整个流程,直到现在,我们还是不明白在上面的流程中,对于文件系统有什么变化,比如说在 /dev/下增加一个 inode 节点, sysfs sys 等相关知识是怎样和 kobject 联系起来的。对于大多数的驱动开发者而言,我想也有很多搞不清楚这种关系,在这一节中,主要讲解这些内容。 Class_create函数就是从来创建名字为 DEVNAME 的类

hello_class = class_create(THIS_MODULE, DEVNAME);

class_device_create 的作用是在 /dev/ 目录下生成 DEVNAME 的节点。

class_device_create(hello_class, NULL, dev, NULL, DEVNAME);

这样创建了设备名字 /dev/DEVNAME,同时在 sysfs 系统中添加了新的文件 /sys/class/DEVNAME/DEVNAME

Sysfs 文件系统为我们提供了 kobject对象层次结构的视图,它被用来表示 kobject ,而 kobject的属性以及 kobject 之间的相互关系,帮助用户可以以一个简单文件系统的方式来观察系统中各种设备的拓扑结构。

首先请出设备模型的主角吧,它就是总线,设备,驱动。在 include/linux/device.h中找到对应的数据结构,表示如下:

struct bus_type {

const char *name; //总线的名字,显示在 /bus/

struct bus_attribute *bus_attrs;

struct device_attribute *dev_attrs;

struct driver_attribute *drv_attrs;

int (*match )(struct device *dev, struct device_driver *drv);// match 函数用于匹配属于这个 bus 的设备和驱动

int (*uevent)(struct device *dev, struct kobj_uevent_env *env);// uevent 用于处理 Linux uevent 事件

int (*probe)(struct device *dev); // 热插拔时设备的探测函数

int (*remove)(struct device *dev); // 移除设备的处理函数

void (*shutdown)(struct device *dev); // 关闭设备

int (*suspend)(struct device *dev, pm_message_t state);// 挂起设备

int (*resume)(struct device *dev); // 恢复设备

const struct dev_pm_ops *pm;

struct bus_type_private *p;

};

开发驱动程序的时候,一般总线接口是不需要考虑的,除非想要实现一个新的总线到系统中。在内核 2.6.35 struct bus_type描述了一个 bus 对象。

struct device_driver {

const char *name;

struct bus_type *bus; // 指针指向总线类型

struct module *owner;

const char *mod_name;/* used for built-in modules */

bool suppress_bind_attrs;/* disables bind/unbind via sysfs */

#if defined(CONFIG_OF)

const struct of_device_id *of_match_table;

#endif

 

int (*probe) (struct device *dev);

int (*remove) (struct device *dev);

void (*shutdown) (struct device *dev);

int (*suspend) (struct device *dev, pm_message_t state);

int (*resume) (struct device *dev);

const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;

};

当一个设备注册到系统中时,它就向系统表明了哪个驱动匹配这个设备。 Linux内核会在注册的设备驱动中查找匹配的驱动并调用对应的探测函数( probe )来初始化设备。

 

struct device {

struct device *parent;

struct device_private *p;

struct kobject kobj;

const char *init_name; /* initial name of the device */

struct device_type *type;

struct mutex mutex; /* mutex to synchronize calls to its driver.*/

struct bus_type *bus;/* 指针指向总线类型 type of bus device is on */

struct device_driver *driver; /* 指针指向设备对应的驱动which driver has allocated this device */

void *platform_data;/* Platform specific data, device core doesn't touch it */

struct dev_pm_info power;

 

#ifdef CONFIG_NUMA

int numa_node; /* NUMA node this device is close to */

#endif

u64 *dma_mask; /* dma mask (if dma'able device) */

u64 coherent_dma_mask;/* Like dma_mask, but for

alloc_coherent mappings as

not all hardware supports

64 bit addresses for consistent

allocations such descriptors. */

struct device_dma_parameters *dma_parms;

struct list_head dma_pools; /* dma pools (if dma'ble) */

struct dma_coherent_mem *dma_mem; /* internal for coherent mem

override */

/* arch specific additions */

struct dev_archdata archdata;

#ifdef CONFIG_OF

struct device_node *of_node;

#endif

 

dev_t devt; /* dev_t, creates the sysfs "dev" */

spinlock_t devres_lock;

struct list_head devres_head;

 

struct klist_node knode_class;

struct class *class;

const struct attribute_group **groups;/* optional groups */

 

void (*release)(struct device *dev);

};

 

我是在 2.6.35 的内核上写这个东东的,发现 bus_type device_driver device 这几个结构里面的内容和我们以前的有很大的不同了,以前这几个结构体之间有所谓的三角关系,就是说, struct bus_type中封装了 device_driver device 类型,而在 device 中有指向 bus_type device_driver的指针,而在 device_driver 中有指向 bus的指针,及一个设备的链表,这些东东都在 2.6.35 中无形的改变,从哪个内核开始改变的,就不说了。下面看看这几个结构体式怎么实现以前的 3角关系的。

对于struct device 结构体而言,我们发现*bus,*driver 都还在,说明这个没有变化,

Struct device_driver 中也有 struct bus_type *bus;变化最大的就是 struct bus_type 了。没有 struct kset devices,struct kset drivers这样变量了。下面看看 bus_type 中的下面 2 个变量是什么东东。

const struct dev_pm_ops *pm;

struct bus_type_private *p;

前面dev_pm_ops 表示电源管理的操作,在#include <linux/pm.h> 中定义如下:

struct dev_pm_ops {

int (*prepare)(struct device *dev);

void (*complete)(struct device *dev);

int (*suspend)(struct device *dev);

int (*resume)(struct device *dev);

int (*freeze)(struct device *dev);

int (*thaw)(struct device *dev);

int (*poweroff)(struct device *dev);

int (*restore)(struct device *dev);

int (*suspend_noirq)(struct device *dev);

int (*resume_noirq)(struct device *dev);

int (*freeze_noirq)(struct device *dev);

int (*thaw_noirq)(struct device *dev);

int (*poweroff_noirq)(struct device *dev);

int (*restore_noirq)(struct device *dev);

int (*runtime_suspend)(struct device *dev);

int (*runtime_resume)(struct device *dev);

int (*runtime_idle)(struct device *dev);

};

接着介绍下 bus_type_private 这个结构,在 /drivers/base/base.h 中找到了这个结构的定义

structbus_type_private{

020structksetsubsys;

021structkset* drivers_kset;

022structkset* devices_kset;

023structklistklist_devices;

024structklistklist_drivers;

025structblocking_notifier_headbus_notifier;

026unsignedint drivers_autoprobe: 1;

027structbus_type* bus;

028};

果然不出我所料,其实bus_type 换汤不换药,只是将drivers devices 使用 bus_type_private 进行又一次封装。分析这里,我们又搞清楚了bus driver device这三者之间的关系了,一个三角关系,通过任何一个都可以找到其它 2个。在热插拔出现前,是先有设备,后有驱动的,总线上挂着 2个链表,一条为 drivers链表,一条为 device链表,每出现一个设备,就向总线报告,每出现一个驱动,也向总线报告,将它插入对应的链表中。当一个新的设备或驱动出现时,首先是向总线报告,然后,就去扫描驱动或设备链表,找到它们心目中的那个她,然后绑定。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐