现在的位置: 首页 > 综合 > 正文

详细解析windows usb驱动和linux usb驱动的相似和差异(五)

2013年09月08日 ⁄ 综合 ⁄ 共 13760字 ⁄ 字号 评论关闭

版权@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*/

抱歉!评论已关闭.