Download this MP3 - (Right Click)
伴着音乐开始Linux设备驱动之旅,接下来几篇日志都将记录“字符设备驱动程序”的编写与测试。
我选择边贴代码边说明的方式来进行分析设计我们的驱动程序,因为这样比较明确清楚。
自定义头文件demo.h如下:
#ifndef _DEMO_H_ #define _DEMO_H_ #include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ /******************************************************** * Macros to help debugging ********************************************************/ #undef PDEBUG /* undef it, just in case */ #ifdef DEMO_DEBUG #ifdef __KERNEL__ # define PDEBUG(fmt, args...) printk( KERN_DEBUG "DEMO: " fmt, ## args) #else//usr space # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else # define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif #undef PDEBUGG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ //设备号 #define DEMO_MAJOR 224 #define DEMO_MINOR 0 #define COMMAND1 1 #define COMMAND2 2 //设备结构 struct DEMO_dev { struct cdev cdev; /* Char device structure */ }; //函数申明 ssize_t DEMO_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t DEMO_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); loff_t DEMO_llseek(struct file *filp, loff_t off, int whence); int DEMO_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); #endif /* _DEMO_H_ */
这里面比较需要说明的是
1: #define DEMO_MAJOR 224
2: #define DEMO_MINOR 0
这里分别为主设备号、次设备号的定义。至于主次设备号的用处一会在源代码里面解释,这里提醒一下。
下面有很多的函数声明,这些函数是写驱动程序都应该有的,接下来解释具体实现。
下面为源文件demo.c
1: #include <linux/module.h>
2: #include <linux/kernel.h>
3: #include <linux/fs.h>
4: #include <linux/errno.h>
5: #include <linux/types.h>
6: #include <linux/fcntl.h>
7: #include <linux/cdev.h>
8: #include <linux/version.h>
9: #include <linux/vmalloc.h>
10: #include <linux/ctype.h>
11: #include <linux/pagemap.h>
12:
13: #include "demo.h"
14:
15: MODULE_AUTHOR("sunnysun");
16: MODULE_LICENSE("Dual BSD/GPL");
17:
18: struct DEMO_dev *DEMO_devices;
19: static unsigned char demo_inc=0;
20: static u8 demoBuffer[256];
这里首先列出的是各种头文件、以及结构体定义、变量定义和宏调用。
头文件再用到里面内容的时候具体说明,如果你好奇也可以直接去我给出的网址看源代码。
MODULE_AUTHOR("sunnysun");
MODULE_LICENSE("Dual BSD/GPL");
这里的两个宏来自
#include <linux/module.h>
第一个宏声明模块作者是谁,关于第二个宏为内核认识的特定许可有, "GPL"( 适用 GNU 通用公共许可的任何版本 ),
"GPL v2"( 只适用 GPL 版本 2 ), "GPL and additional rights", "Dual BSD/GPL", "Dual MPL/GPL", 和 "Proprietary". 除非你的
模块明确标识是在内核认识的一个自由许可下, 否则就假定它是私有的, 内核在模块加载时被"弄污浊"了。
关于类似的宏有以下:
MODULE_DESCRIPION( 一个人可读的关于模块做什么的声明 ),
MODULE_VERSION ( 一个代码修订版本号; 看 <linux/module.h> 的注释以便知道创建版本字串使用的惯例),
MODULE_ALIAS ( 模块为人所知的另一个名子 ),
MODULE_DEVICE_TABLE ( 来告知用户空间, 模块支持那些设备 ).
下面说一下:
struct DEMO_dev *DEMO_devices;
可以看到这个结构体来自demo.h头文件
struct DEMO_dev
{
struct cdev cdev; /* Char device structure */
};
看到这会感觉这个struct cdev从那跑出来的,其实这个结构体正是Linux下的字符设备的结构体,char device的简写。
到Linux内核中找到 linux/include/linux/cdev.h 可以找到这个结构体的实现:
1: struct cdev {
2: struct kobject kobj;
3: struct module *owner; //所属模块
4: const struct file_operations *ops;
5: //文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
6: struct list_head list;
7: dev_t dev;//设备号
8: unsigned int count;
9: };
看这个结构体的时候,又发现了新结构体kobject
这个结构体稍后说明,先说明struct cdev的使用。下面一组函数用来对cdev结构体进行操作:
void cdev_init(struct cdev *, const struct file_operations *);
初始化,建立cdev和file_operation 之间的连接。
struct cdev *cdev_alloc(void);
动态申请一个cdev内存。
void cdev_put(struct cdev *p);
释放。
int cdev_add(struct cdev *, dev_t, unsigned);
注册设备,通常发生在驱动模块的加载函数中。
void cdev_del(struct cdev *);
注销设备,通常发生在驱动模块的卸载函数中。
在注册时应该先调用:
int register_chrdev_region(dev_t from,unsigned count,const char *name)
此函数可以用下面函数进行代替:
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
他们之间的区别在于:register_chrdev_region()用于已知设备号时,另一个用于动态申请,其优点在于不会造成设备号重复的冲突。
在注销之后,应调用:
void unregister_chrdev_region(dev_t from,unsigned count)
函数释放原先申请的设备号。
它们之间的顺序关系如下:
register_chrdev_region()-->cdev_add() //此过程在加载模块中
cdev_del()-->unregister_chrdev_region() //此过程在卸载模块中
cdev_add() 函数的原型如下:
#include <linux/cdev.h>
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
第 1 个参数为 cdev 结构体指针;
第 2 个参数为 dev_t 类型表达的设备号(dev_t 类型表达的设备号用 MKDEV(int major, int minor) 宏来实现) ;
第 3 个参数是设备号的数目。
cdev_add() 函数的实现代码为:
1: /**
2: * cdev_add() - add a char device to the system
3: * @p: the cdev structure for the device
4: * @dev: the first device number for which this device is responsible
5: * @count: the number of consecutive minor numbers corresponding to this
6: * device
7: *
8: * cdev_add() adds the device represented by @p to the system, making it
9: * live immediately. A negative error code is returned on failure.
10: */
11: int cdev_add(struct cdev *p, dev_t dev, unsigned count)
12: {
13: p->dev = dev;
14: p->count = count;
15: return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
16: }
一般的,count 的值为 1,但是有些情形也可能是大于 1 的数。比如 SCSI 磁带机,它通过给每个物理设备安排多个此设备号来允许用户在应用程序里选择操作模式(比如密度)。
cdev_add 如果失败了,那么返回一个负值,表明驱动无法加载到系统中。然而它一般情况下都会成功,一旦 cdev_add 返回,设备也就 “活” 了起来,于是所对应的操作方法(file_operations 结构里所定义的各种函数)也就能为内核所调用。
至此,这个结构体说明结束。接下来说明kobject结构体的实现以及应用。