一、设备驱动之数据结构:
1、dev_t :设备编号,包含主设备号、次设备号。
dev_t是一个32位的数,12位表示主设备号,20为表示次设备号
(1)主设备号 = MAJOR(dev_t dev)
(2)次设备号 = MINOR(dev_t dev)
(3)设备编号 = MKDEV(int major,int minor)
2、file_operations:设备驱动向内核提供的操作函数指针结构
static const struct file_operations XXX_fops =
{
.owner = THIS_MODULE, //拥有该结构的模块指针,防止使用时被卸载
.llseek = XXX_llseek, //定位函数指针
.open = XXX _open, //打开设备函数指针
.read = XXX _read, //读取设备内容,复制内核空间内容到用户空间
.write = XXX _write, //写入到设备,复制用户空间内容到内核空间
.ioctl =XXX _ioctl, //I/O操作函数指针
.release = XXX _release, //释放设备函数指针
};
3、file:文件结构体代表一个打开的设备文件
struct file
{ …//字符设备不相关的省略
const struct file_operations *f_op; //文件支持的操作
unsigned int f_flags; //文件标志
fmode_t f_mode; //文件的读写模式
loff_t f_pos; //当前的读写位置
struct fown_struct f_owner;
void *private_data;//文件私有数据指针,同设备结构体关联
};
4、inode:信息节点结构,索引节点。管理文件的属性(访问权限、大小、最后修改时间等)
struct inode
{…省略不相关
umode_t i_mode; //文件模式,标识字符设备还是块设备
dev_t i_rdev; //设备编号
struct list_head i_devices; //块设备链表
union
{
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev; //块设备指针
struct cdev *i_cdev; //字符设备指针
};
void *i_private; //设备私有数据指针
};
5、cdev:字符设备标准结构体
struct cdev
{
struct kobject kobj; //内嵌内核对象
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作
struct list_head list; //包含该类设备的所有inode
dev_t dev; //设备编号
unsigned int count; //从设备数量
};
二、设备驱动之内核函数
设备驱动实现流程:
1、分配设备号
//动态分配设备号
int alloc_chrdev_region
(dev_t *dev, //设备号
unsigned int firstminor, //第一个次设备号
unsigned int count, //所请求连续设备编号的个数。
char *name //设备名称
)
//静态分配设备号
int register_chrdev_region
( dev_t first, //预分配的设备号
unsigned int count,//连续设备编号的个数
char* name//设备名称
)
2、设备体结构分配空间
void * kmalloc (size_t size, int flags);
size:分配内存大小
flags:要分配内存的类型;值为GFP_KERNEL:表示不能立刻分配内存时,要等待
3、 注册设备
//初始化设备
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
//添加设备到系统,激活设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
4、释放和注销设备
cdev_del(cdev); //注销cdev,即从系统中删除此设备
kfree(devp); //释放结构设备体内存
unregister_chrdev_region(dev_t, 1); //释放设备号
5、定义设备文件操作函数
打开、读写、设备控制等
6、声明设备的初始化函数和销毁函数
module_init(xxx_init);
module_exit(xxx_exit);
三、设备驱动实现流程探析
1、根据linux规范编写驱动程序:声明、实现驱动的初始化函数和退出函数;实现设备文件操作函数。
2、加载驱动:insmod xxx.so –对应执行模块的初始化函数xxx_init,初始化驱动,同时把设备添加到系统,同时激活设备。这时可以通过lsmod和cat /proc/devices查看
3、创建设备节点,相当于在文件目录中添加设备文件
通过Sudo mknod /dev/testdev c 254 0(设备文件名、类型为字符设备、主设备号为254、次设备开始序号为0)命令,创建了/dev/testdev设备节点。
4、命令行操作设备:
写数据到设备:echo hello>/dev/testdev 命令终端通过操作系统调用内核驱动的写函数,把用户空间的数据写到内核空间的设备上。
读数据到终端:cat /dev/testdev命令终端通过操作系统调用内核驱动的读函数,把设备上的数据读到命令行终端。
卸载:rmnod:卸载设备,通过操作系统调用设备驱动的卸载函数
5、用户应用程序操作:
a.先打开设备文件,获取文件句柄:
myfile = open("/dev/testdev",O_RDWR):向操作系统,请求打开文件,并传递设备文件的全路径,操作系统调用驱动的open打开函数,并传递该设备相关的inode参数和file参数(在设备加载和创建设备节点时已产生)。
b.写文件:write(myfile,"hello,",sizeof("hellor"));
调用文件系统的标准写文件函数,通过操作系统,调用内核的设备驱动的写函数,并把参数转换为内核驱动写函数的标准参数。内核驱动的写函数,通过调用内核的函数,从用户空间向内核空间写数据。
C.读文件:read(myfile,buffer,size);
通过操作系统,调用内核的设备驱动的读函数,通过内核的函数,从内核空间向用户空间的buffer空间写数据。
d.设备控制:int ioctl(int fd, ind cmd, …):
通过操作系统调用内核设备驱动的ioctl函数即可。
e. 关闭文件:close(myfile):操作系统做相应的清理工作,但并不卸载设备驱动。
四、设备驱动程序编写实战