字符设备指的是在I/O传输过程中以字符为单位进行传输的设备,例如键盘、打印机等。注意,以字符为单位并不一定意味着以字节为单位,因为编码有不同规定,有的一个字符占用两个字节。
在linux中一切都是文件,所以设计驱动的最终目的是为了迎合上层系统调用对于设备文件的操作。
系统调用中对于文件的操作主要有以下几个:
int create(const char*filename, mode_t mode)
int open(const char *pathname, int flags,mode_t mode)
int read(int fd, const void *buf, size_t length)
int write(int fd, const void *buf, size_t length)
int lseek(int fd,offset_t,int whence)
int close(int fd)
这些函数当然也可以使用C函数库实现同样的功能,使用C函数库也更易于移植。
底层驱动主要是实现一系列与这些函数相对应的功能,以便于这些函数来进行调用。而底层函数与上层关联所用到的是一个结构体struct file_operation。
因为本章主要讲解字符驱动,所以不对这些函数做详述,感兴趣的可以百度之。
请记住以下几个结构体和函数,我们将在接下来的驱动代码中用到:
宏:
MAJOR(dev_t dev) //获取主设备号
MINOR(dev_t dev) //获取次设备号
MKDEV(int major, int minor) //生成dev_t
结构:
struct cdev{
struct kobject kobj;
struct module* owner; //所属模块
struct file_operations *ops; //文件操作结构体
stuct list_head list;
dev_t dev; //设备号
unsigned int count;
}; //字符设备结构体,用来描述一个字符设备
struct file_operations xxx_ops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.....
}; //文件操作映射函数结构体
函数:
void cdev_init(struct cdev*, struct file_operations *); //初始化结构体cdev
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); //以上两个函数用于申请设备号
void unregister_chrdev_region(dev_t from,unsigned count) //释放设备号
字符设备驱动也是linux模块开发的一部分,所以需要遵循模块开发的规则,在module_init()里注册初始化函数static int xxx_init(),在module_exit()里注册退出函数static void xxx_exit()。
在xxx_init()中实现设备号的申请和cdev的注册,在xxx_exit()中注销cdev和释放设备号。
注册的流程为:
申请设备号—>申请cdev—>实现file_operations—->cdev初始化—–>cdev的注册
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); //申请设备号
cdev_init(mdev, &xxx_fops); //cdev初始化
ret = cdev_add(m_dev, xxx_dev_no, 1); //注册设备
卸载的流程为:
卸载cdev—–>释放设备号
cdev_del(m_dev); //注销设备
unregister_chrdev_region(xxx_dev_no, 1); //释放设备号
在linux内核开发中,我们常常定义一个结构体,此结构体包含了设备设计的cdev,私有数据、锁等信息。例如:
struct xxx_dev{
struct cdev cdev,
int flags,
.........
}xxx_dev;
在注册的时候申请这个结构体的空间,并实现里面的实体。
这里实现一个led灯开启和关闭的字符设备驱动:
#include ....必要的linux头文件
struct led_dev {
struct cdec cdev;
unsigned int value;
};
#define LED_MAJOR 123
struct led_dev * led_devp; //声明结构体
/* 实现file_opertaions中的函数 */
static int led_open(struct inode* inode, struct file* filp) {
filp->private_date=global_dev; //赋值私有数据 *** 重要
return 0;
}
static int led_release(struct inode* inode, struct file* filp) {
return 0;
}
static ssize_t led_read(struct file* filp, char _user *buf , size_t count, loff_t * f_pos) {
......
}
static ssize_t led_write(struct file* filp, const char _user *buf , size_t count, loff_t * f_pos) {
......
}
static int led_ioctl(struct file* filp, unsigend int cmd,unsigned long arg) {
......
}
struct file_operations led_ops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.unlocked_ioctl = led_ioctl,
.open = led_open,
.release = led_release,
}; //文件操作映射函数结构体
/* 注册cdev结构体 */
static void led_setup_cdev(struct led_dev* dev,int index) {
int err,devno = MKDEV(LED_MAJOR,index);
cdev_init(&dev->cdev, &led_ops);
dev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err)
printk(KERN_NOTICE”Erro %d adding LED%d”,err, index);
}
/* 模块加载函数 */
static int led_init(void) {
int result;
dev_t dev = MKDEV(LED_MAJOR,0);
/* 申请字符设备号 */
if(LED_MAJOR)
result = register_chrdev_region(dev, 1, “LED”);
else{
result = alloc_chrdev_region(&dev,0, 1, “LED”);
LED_MAJOR = MAJOR(dev);
}
if(result < 0)
return result;
/* 申请结构体空间 */
led_devp = kzmalloc(sizeof(struct led_dev), GFP_KERNEL);
if(!led_devp) {
result = -ENOMEM;
goto fail_malloc;
}
led_setup_cdev(led_devp,0);
return 0;
fail_malloc:
unregister_chrdev_region(dev,1);
return result;
}
/* 模块卸载函数 */
static void led_exit(void) {
cdev_del(&led_dev->cdev); //删除字符设备结构体
kfree(led_dev); //释放给led_dev分配的内存
unregister(MKDEV(LED_MAJOR,0), 1); //释放字符设备号
}
module_init(led_init); //加载
module_exit(led_exit); //卸载
/* file_operations里面的函数自己实现,这里主要讲解注册与卸载的流程 */
struct file结构体中有一个字段void *private_data,实际操作中我们常常把这个字段赋值为我们自己定义的结构体struct xxx_dev,这样我们在实现file_operations时可以直接从file中获取结构体和我们想要的数据。
赋值:
static int xxx_open(struct inode* inode, struct file* filp) {
filp->private_data=led_devp;
return 0;
}
获取:
static ssize_t led_read(struct file* filp, char _user *buf , size_t count, loff_t * f_pos) {
struct led_dev led_devp = filp->private_data;
.......
}
Reference: