版权@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 函数联系以来了,但是具体是怎么联系起来的呢,内核起到了怎样的作用,大多数的驱动开发者都是一只半解。
-
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,
};
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*/