一、字符设备驱动程序分析
1、cdev
cdev是描述字符设备的结构体,要设计一个简单的linux字符设备,就要先定义一个cdev结构体,然后对结构体进行初始化,初始化结束后再将cdev注册到内核,这样内核就添加了一个字符设备驱动。
cdev的原型如下:
#include <linux/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结构体成员的分析
1)struct kobject kobj;
linux在做字符设备管理的时候,使用的驱动模型,利用kobject生成/sys下的驱动信息;
2)truct module *owner;
这个字符设备输入到那一个module中,一般设为THIS_MODULE;
3)const struct file_operations *ops;
文件操作集,给应用程序提供API接口,应用程序通过系统调用来访问文件操作集中的接口函数;
文件操作集中有很多函数,下面几种较为常用:
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
注意:
文件IO函数原型的参数,与文件操作集中对应的函数的参数是否一致?
不一致,因为系统调用过程中间经过vfs华为linux内核,过程较复杂,与函数的参数调用不同。
4) struct list_head list;
内核双向链表,向内核中注册一个字符设备时,链表增加一项
5)dev_t dev;
设备号
2、设备号
1)设备号的定义
dev_t dev_num;
在linux内核中,每一个设备驱动都有一个设备号,设备号由主设备号和次设备号组成,个人理解如下:
主设备号:描述设备驱动的类型;
次设备号:描述具体设备驱动类型的一个应用实例;
例如:嵌入式开发板中nand flash的5个分区
[root@Huang /]# ls /dev/mtdblock* -l
brw-rw---- 1 root root 31, 0 Jan 2 11:21 /dev/mtdblock0
brw-rw---- 1 root root 31, 1 Jan 2 11:21 /dev/mtdblock1
brw-rw---- 1 root root 31, 2 Jan 2 11:21 /dev/mtdblock2
brw-rw---- 1 root root 31, 3 Jan 2 11:21 /dev/mtdblock3
brw-rw---- 1 root root 31, 4 Jan 2 11:21 /dev/mtdblock4
设备号是一个32位无符号的整型值,其中高12位是主设备号,低20位是次设备号。
设备号与主次设备号之间的转换:
dev_num = MKDEV(major, minor); /*由主次设备号得到设备号*/
major = MAJOR(dev_num); /*由设备号得到主设备号*/
minor = MINOR(dev_num); /*由设备号得到次设备号*/
2)如何获得一个设备号
设计设备驱动时,需要获得一个可用的设备号,一般有两种方法:静态注册设备号、动态分配设备号。所谓静态注册就是我们给定一个设备号,如果该设备号在内核中 没被使用,那我们通过静态注册就可以用该设备号作为驱动的设备号;动态分配是指让内核给我们分配一个可用的设备号。
·静态注册函数:
int register_chrdev_region(dev_t from, unsigned int count, const char *name);
参数分析:
dev_t from:想要注册的设备号;
unsigned int count:次设备的个数;
const char *name:设备的名称;
返回值:
注册成功,返回0;
注册失败,返回一个负数的错误码;
动态分配函数:
int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name);
参数分析:
dev_t *dev:动态分配后的设备号;
unsigned int baseminor:第一个次设备号;
unsigned int count:次设备的个数;
char *name:设备的名称;
返回值:
分配成功,返回0;
分配失败,返回一个负数的错误码;
二、字符设备驱动程序设计
设计思路为:定义字符设备(cdev)-->定义设备号(cdev_num)-->申请设备号(静态或动态)-->定义并设计文件操作集函数-->初始化字符设备-->将字符设备添加到内核中-->出错或退出时要注销字符设备和设备号;
1)各种定义:
static struct cdev cdev_xxx;
static dev_t dev_num;
static cdev_num = “xxx”;
static int cdev_major = 0;
static int cdev_minor = 0;
......
static struct file_operations cdev_fops = {
.open = test_open,
.read = test_read,
.write = test_write,
.iocti = test_ioctl,
.release = test_release,
};
注意:文件操作集的定义及初始化要放在各种接口函数的设计之后;
2)申请设备号;
if(cdev_major)/*若定义的主设备号不为0,则静态注册设备号*/
{
cdev_num = MKDEV(cdev_maor, cdev_minor);
cdev_num = register_chrdev_region(cdev_num, 1, cdev_name );
}
else/*若定义的主设备号为0,则动态分配设备号*/
{
ret = alloc_chrdev_region(&cdev_num, cdev_minor, 1, cdev_name);
cdev_major = MAJOR(cdev_num);
if(ret < 0)
{
printk(KERN_WARNING“alloc chrdev_num error,cannot get cdev_major %d\n”, cdev_major);
goto err_chrdev_region; /*出错处理*/
}
}
3)初始化字符设备:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
struct cdev *cdev: /*定义的字符设备*/
const struct file_operations *fops: /*已经完成定义和初始化的一个文件操作集*/
cdev_init(&cdev_xxx, &cdev_fops);
4)将字符设备加入到内核中
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
struct cdev *p :定义并初始化的字符设备cdev
dev_t dev:已经申请到的设备号
unsigned count:次设备的数量
返回值:
注册成功,返回0
注册失败,返回一个负数的错误码
代码:
ret = cdev_add(&cdev_xxx, cdev_num, 1);
if(ret < 0)
{
printk(“cdev_add error!\n”);
goto err_cdev_add;
}
5)注销字符设备
void cdev_del(struct cdev *p);
参数:
struct cdev *p:注册的设备;
6)注销设备号
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
dev_t from:注册的设备号(注册的一个设备号的开始值);
unsigned count: 次设备的个数;
三、字符设备驱动的调试
写一个应用程序,利用应用程序调试驱动程序。
1、安装驱动
[root@Huang /test]# insmod demo1.ko
[24273.778642]
[24273.779733] this is the first demo of driver, inserting successful !
2、查看内核模块
[root@Huang /test]# lsmod
demo1 2589 0 - Live 0xbf000000
3、查看字符设备的主设备号和设备名称
char drv_name[ ]="chrtest";
int TestMajor = 0;//主设备号
int TestMinor = 0;//次设备号
[root@Huang /test]# cat /proc/devices
Character devices:
250 chrtest --->主设备号和设备名称
4、手动创建设备文件(可以自动创建设备文件,后面再写)
[root@Huang /test]# mknod /dev/chr_test1 c 250 0
5、查看设备文件
[root@Huang /test]# ls /dev/chr_test1 -l
crw-r--r-- 1 root root 250, 0 Jan 2 18:14 /dev/chr_test1
6、执行应用程序
7、驱动的卸载
[root@Huang /]# rmmod demo1
[25293.152108] the driver is exiting
哈,一个简单的字符设备驱动就写完啦,代码比较简单,就不全部贴不来啦。
本人新手,请多指教哈。