04. 字符设备驱动(上)

之前我们说过,Linux设备主要分为三类:字符设备、块设备和网络接口。

字符设备相对于另外两个设备更加容易理解,同时,这类设备也适合大多数简单的硬件设备,因此,接下来我们学习一下字符设备驱动。

字符驱动的学习分为两节:先介绍一下字符驱动驱动中的一些概念,这些概念有些在块设备中也是相同的;然后基于系统的内存创建一个字符设备实例。

本节主要介绍一下驱动中的一些概念,主要学会以下内容:

  • 设备号及其相关操作
  • 文件操作相关的数据结构
  • 文件相关的数据结构
  • 节点相关的数据结构

1. 设备号及其相关操作

字符设备通常位于 /dev 目录下,通过设备的名称进行访问,这些名称称为设备文件或节点。在 /dev 目录下,使用 ls -l 命令来查看系统中的设备情况,部分结果如下:

ls -l 查看设备情况

在每行的最前面的有一个字符(cdlb等),其中,c 开头的设备表示的就是字符设备。(类似的,b 开头的设备表示块设备、d 表示是一个目录、l 表示是一个连接)

截图中的字符设备为 btrfs-control,具体信息如下:

crw-------   1 root    root     10, 234 7月  25 10:45 btrfs-control

其中的:10,234 表示的就是设备的设备号,10 为主设备号(misc 字符设备),234为次设备号。

主设备号用于表示设备对应的驱动程序。Linux 内核允许多个驱动程序共享主设备号,但许多设备仍然按照一个设备号对应一个驱动程序的原则组织;次设备号由内核使用,用于正确确定设备文件所指的设备,利用次设备号可以获取一个指向内核设备的直接指针,也可以将次设备号当做设备本地数组的索引。

<linux/types.h> 头文件中提供了一个 dev_t 类型来保存设备号——在2.6.0内核中,dev_t 类型是32位的,其中的12位用来表示主设备号,剩余20位用来表示次设备号。但我们不要默认永远都是这样实现的,而应该利用 <linux/kdev_t.h> 中定义的两个宏来获取 dev_t 所表示的设备号:

MAJOR(dev_t dev); //返回主设备号
MINOR(dev_t dev);  //返回次设备号

同样,当知道主设备号和次设备号时,也不要自己手动去拼接生成 dev_t 变量,而应使用以下宏实现:

MKDEV(int major, int minor); //返回dev_t变量

在驱动程序中,对于设备号的相关操作主要是分配和释放,操作函数都在 <linux/fs.h> 中进行了声明,主要有以下几个函数:

int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);

对其中的参数进行说明:

  • register_chrdev_regionfirst 是要分配的设备编号的起始值;count 是请求的连续设备编号的个数;name 是和该设备号关联的设备名称,它将出现在 /proc/devicessysfs 中。
  • alloc_chrdev_regiondev 用于输出分配的设备编号;firstminor 为要是用的被请求的第一个次设备号,通常为0;countnameregister_chrdev_region
  • unregister_chrdev_regionfirstcountregister_chrdev_region

register_chrdev_region 看名字就知道是注册设备号,这个函数适用于注册所需设备编号已知的情况下;在大多数情况下,我们是不知道设备编号的,可以使用 alloc_chrdev_region 来分配设备编号;unregister_chrdev_region 看名字也知道,是释放注册的设备编号,一般在退出函数中调用。

对于 register_chrdev_regionalloc_chrdev_region,强烈建议使用 alloc_chrdev_region 来分配设备编号,这样可以使得驱动程序适应性更强。

分配设备编号是驱动代码需要完成的第一件事,后续还需要将内部函数和设备编号连接起来。

2. 文件操作相关的数据结构

文件操作相关的数据结构主要是结构体 struct file_operations,其定义在 <linux/fs.h> 中。

该结构体包含了一组函数指针,用于将内部函数和上面的设备编号连接起来。

该结构体的具体内容请自行查看源码,下面对平常用得比较多的几个函数进行说明:

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 *);
    ......
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    ......
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (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 *);
    ......
} ;
  • owner 是一个模块指针,指定该结构体的拥有者模块,通常被初始化为 THIS_MODULE
  • read:从设备中读取数据,返回负值表示读取出错,返回非负值表示读取到了多少字节;
  • write:往设备中写入数据,返回写入成功的字节数,返回负数表示写入出错;
  • poll:是 pollepollselect这三个系统调用的后端实现,用于实现IO的多路复用;
  • unlocked_ioctlcompat_ioctl:主要是unlocked_ioctl,用于系统调用ioctl执行设备的特定命令,而 compat_ioctl 主要用于32位应用程序在64位的操作系统中时执行,一般不用管;
  • mmap:用于请求将设备内存映射到进程地址空间;
  • open:设备文件执行的第一个操作,定义为 NULL 表示打开设备永远成功;
  • release:当file结构体被释放时调用。
    目前只要知道有这个结构体即可,可以在后续驱动中需要使用的时候再去深入了解。

3. 文件相关的数据结构

文件相关的数据结构主要是 struct file 结构体,其定义在 <linux/fs.h> 中。

该结构体表示一个打开的文件,它由内核在 open 时创建,因此系统中每个打开的文件、设备都有一个对应的 file 结构体。在文件的所有实例都被关闭后,内核才会释放这个结构体。

具体内容查看源码,下面对比较重要的属性进行说明:

struct file {
    ......
    const struct file_operations *f_op;
    ......
    unsigned int f_flags;
    fmode_t f_mode;
    ......
    loff_t f_pos;
    ......
    void *private_data;
    ......
}
  • f_op:文件操作相关。内核在执行 open 操作的时候对这个指针赋值,以后需要处理这些操作的时候读取这个指针;
  • f_flags:文件标志,如 O_RDONLYO_NONBLOCKO_SYNC。主要是用来检查 O_NONBLOCK 标志,其他标志很少用。检查读写权限不是用这个变量,而是需要使用下面的 f_mode
  • f_mode:文件模式。用来标识文件是否可读或可写(或可读写);
  • f_pos:表示当前的读写位置,是一个 long long 型的变量,可读,但不要去直接去修改它;
  • private_data:是一个 void 型的指针,可以忽略,也可以根据需要用于存储需要的数据,有时候非常有用。

同样,这个结构体目前只需要了解即可,后续如果有需要再深入了解。

4. 节点相关的数据结构

上面介绍了 struct file_operations 结构体表示文件操作相关的结构体,struct file 结构体表示打开的文件。下面介绍的 struct inode 结构体表示的是文件(节点)。该结构体同样位于 <linux/fs.h> 中。

struct inode 中包含了大量的有关文件的信息,其中对于驱动程序编写非常重要的主要有以下几个:

struct inode {
    ......
    dev_t i_rdev;
    ......
    union {
        ......
        struct cdev *i_cdev; 
        ......
    };
    ......
};
  • i_rdev:包含了设备编号;
  • i_cdev:当 inode 指向一个字符设备时,该字段包含了指向 struct cdev 结构体的指针。

以上是本节的主要内容,下一节将会编写一个字符设备实例。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容