Linux内核分析(五)----字符设备驱动实现

时间:2022-03-18 11:07:44

Linux内核分析(五)

昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题。

今天我们会分析到以下内容:

1.      字符设备驱动基础

2.      简单字符设备驱动实现

3.      驱动测试

 

l  字符设备基础

1.       字符设备描述结构

在linux2.6内核中,使用cdev结构体描述一个字符设备,其定义如下:

 

Linux内核分析(五)----字符设备驱动实现
1 struct cdev {2     struct kobject kobj;/*基于kobject*/3     struct module *owner; /*所属模块*/4     const struct file_operations *ops; /*设备文件操作函数集*/5     struct list_head list; 6     dev_t dev; /*设备号*/7     unsigned int count; /*该种类型设备数目*/8 };
Linux内核分析(五)----字符设备驱动实现

上面结构中需要我们进行初始化的有ops和dev,下面我们会对这两个成员进行分析。

注:kobject结构是驱动中很重要的一个结构,由于其复杂性,我们现在不进行介绍,后面会详细介绍。

2.       设备号

1.        何为设备号:cdev结构体中dev成员定义了设备号,而dev_t则为U32类型的也就是32位,其中12位为主设备号,20位为次设备号。我们执行ls –l /dev/可看到下图,其中左边红框为主设备号,右边为次设备号

Linux内核分析(五)----字符设备驱动实现

2.        何为主设备号:用来对应该设备为何种类型设备。(比如串口我们用一个数字识别,而串口有好几个)

3.        何为次设备号:用来对应同一类型设备下的具体设备。(用次设备号来具体区分是哪个串口)

4.        设备号相关操作:

1.        通过主设备号和次设备号获取dev:dev = MKDEV(主,次);

2.        通过dev获取主设备号:主 = MAJOR(dev);

3.        通过dev获取次设备号:dev = MINOR(dev);

5.        设备号分配:设备号的分配有两种方式,一种是静态的,另一种是动态的,下面一一分析

1.        静态分配:也就是程序员自己指定设备号,通过register_chrdev_region();函数向内核申请,可能会导致和内核已有的冲突,从而失败。

2.        动态分配:通过 alloc_chrdev_region(); 函数向内核申请设备号。

3.        释放设备号:通过 unregister_chrdev_region(); 释放申请到的设备号。

3.       file_operations操作函数集

file_operations结构体中的成员函数在我们驱动开发过程中极为重要,其中的内容相当庞大,下面我们看看其定义:

 

Linux内核分析(五)----字符设备驱动实现 View Code

上面结构体中的函数指针所指向的函数,在我们在进行open、write、read等系统调用的时候最终会被调用到,所以我们的驱动中想为应用层实现那种调用就要在此实现。

4.       字符设备驱动初始化

我们通过上面的分析对设备号和操作函数集有了一定的了解下面我们来看字符设备驱动初始化,其主要步骤如下。

1.        分配cdev结构:有静态(直接定义)动态(cdev_alloc();)两种方式

2.        初始化cdev结构:使用 cdev_init(struct cdev *cdev, const struct file_operations *fops) 初始化

3.        驱动注册:使用 int cdev_add(struct cdev *p, dev_t dev, unsigned count)//count为该种类型的设备个数注册 

4.        硬件初始化:阅读芯片手册进行硬件设备的初始化

5.        完成操作函数集:实现要用的操作(设备方法)

6.        驱动注销:使用 void cdev_del(struct cdev *p) 注销

5.       字符设备驱动模型及调用关系

下面我通过一张图将字符设备的驱动结构、以及字符设备驱动与用户空间的调用关系进行展示:

Linux内核分析(五)----字符设备驱动实现

6.       遗漏知识

我们内核空间和用户空间的数据交互要用到下面两个函数:

1 copy_from_user();//从用户空间读2 copy_to_user();//写入用户空间 

l  简单字符设备驱动实现

经过上面的分析我们对字符设备有一定了解,下面我们来完成一个最简单的字符设备驱动。我只展示最主要的代码,整个项目工程https://github.com/wrjvszq/myblongs.git欢迎大家关注。

1.       字符设备驱动编写

因为驱动本身就是一个内核模块,下面的字符设备驱动只实现了部分方法,在后面的博客中我们会基于此驱动慢慢修改,希望大家掌握。

 

Linux内核分析(五)----字符设备驱动实现 View Code

l  驱动测试

经过上面的代码我们已经实现了一个简单的字符设备驱动,我们下面进行测试。(应用程序在https://github.com/wrjvszq/myblongs.git 上)

1.       加载内核模块

我们使用 insmod memdev.ko 命令加载内核模块

2.       获取设备号

我们的设备号是动态申请到的,所以我们要通过下面的命令查看设备号

 cat /proc/devices 

找到我们的设备memdev的设备号

Linux内核分析(五)----字符设备驱动实现

3.       建立设备文件

使用如下命令建立设备文件

 mknod /dev/文件名 c 主设备号次设备号 

上面命令中文件名为我们在应用程序中打开的文件名

c代表字符设备

主设备号为上一步找到的,我的位249

次设备号非负即可,但不能超过自己所创建的设备数。

比如我的就是 mknod /dev/memdev0 c 249 0 

4.       编译应用程序并测试

使用gcc对应用程序进行编译,然后先使用write对设备进行写入,在使用read对设备读取,完成测试。