【嵌入式Linux驱动开发】三、字符设备驱动(一)

时间:2021-07-19 18:42:40

1. 基本步骤

(1)确定主设备号和次设备号

(2)实现字符驱动程序

  • 实现file_operations结构体;
  • 实现初始化函数,注册字符设备;
  • 实现销毁函数,释放字符设备;
  • 实现字符设备其他基本成员函数。

(3)创建设备文件节点

2. 什么是主设备号/次设备号

主设备号是内核识别一个设备的标识。它是一个整数(占12位),通常使用1~255。

次设备号由内核使用,用于正确确定设备文件所指的设备。它也是一个整数(占20位),通常使用0~255。

注:同一类设备的主设备号相同,不同的是次设备号。如多个串口的主设备号是相同的,次设备号不同。

3. 设备编号的内部表达

(1)dev_t类型(32位):用来保存设备编号

(2)从dev_t获得主、次设备号:

  • MAJOR(dev_t):    //主
  • MINOR(dev_t);    //次

(3)将主、次设备号转换成dev_t类型:

  • MKDEV(int major, int minor);

4. 分配设备号

通常在模块加载函数中调用。

(1)手工分配:找一个内核没有使用的主设备号来使用

#include<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
//first是设备号,次设备号通常为0;count是要分配设备号的个数,即次设备号个数;name是此类设备的名字。

(2)动态分配:

#include<linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
//dev是输出的设备号;firstminor是要申请第一个次设备号;count是要申请的设备号个数;name是此类设备的名字。

5. 释放设备号

通常在模块清理函数中调用。

#include<linux/fs.h>
void unregister_chrdev_region(dev_t dev, unsigned int count);

6.  重要结构体

(1)cdev结构体

struct cdev
{
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
操作cdev结构体的函数:

void cdev_init(struct cdev *cdev, struct file_operations *ops);
//用于初始化已分配的cdev结构,并建立cdev和file_operations之间的连接

struct cdev *cdev_alloc(void);
//用于动态申请一个cdev内存

int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);
//向内核添加一个cdev,完成字符设备的注册,通常在模块加载函数中调用

void cdev_del(struct cdev *cdev);
//删除一个cdev,完成字符设备的注销,通常在模块卸载函数中调用

(2)file_operations结构体

  • 是字符驱动和内核的接口,在include/linux/fs.h中定义;
  • 字符驱动都要实现一个file_operations结构体;

file_operations的主要成员:

struct module *owner:指向模块自身(THIS_MODULE)

open:打开设备

release:关闭设备

read:从设备上读数据

write:向设备上写数据

ioctl:I/O控制函数

llseek:定位当前读写位置指针

mmap:映射设备空间到进程的地址空间

(3)file结构体

  • file_operations结构相关的一个结构体,描述一个正在打开的设备文件。

file的成员:

loff_t  f_pos:当前读写位置

unsigned  int  f_flags:标识文件打开时是否可读或可写,O_RDONLY、O_NONBLOCK、O_SYNC

struct  file_operations *f_op:文件相关的操作,指向所实现的struct  file_operations

void  *private_data:私有数据指针,驱动程序可以将这个字段用于任何目的或者忽略(设为NULL)这个字段

(4)inode结构体

  • 内核用inode结构在内部表示文件;
  • inode与file的区别:file表示打开的文件描述符,多个表示打开的文件描述符的file结构可以指向一个inode结构。

inode的重要成员:

dev_t  i_rdev:对表示设备文件的inode结构,该字段包含了真正的设备号

struct  cdev  *i_cdev:表示字符设备在内核中的内部结构

7. 字符设备驱动程序模版

//字符设备驱动模块加载函数
static int __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev
xxx_dev.cdev.owner = THIS_MODULE;
//获取字符设备编号
if(xxx_major)
{
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
}
else
{
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}
ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
...
}

//字符设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
cdev_del(&xxx_dev.cdev); //注销设备
...
}