栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux内核学习(二)编写最简单的字符设备驱动

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux内核学习(二)编写最简单的字符设备驱动

目录
  • 写在前面
  • 整体环境
  • 学习笔记
    • 模块
    • 字符设备驱动的代码
    • 字符设备驱动的Makefile(构建模块)
      • 管理模块的位置
    • 驱动的插入和卸载

写在前面

之前做项目的时候,有前辈告诉自己,要去学一下Linux内核,对很多方面都有帮助,现在闲下来,来花时间学一下这一部分的知识点,也算是一个学习笔记
目前跟着B站UP主——简说linux 的教程《Linux内核开发100讲》学习,链接如下:
简说linux个人空间
本章参考学习的链接如下:
Makefile中($(KERNELRELEASE),)执行分析
《Linux内核设计与实现》

整体环境

为了学习代码,我们需要一个一套Linux环境,因为为了方便自己记笔记和学习,没有用双系统,直接在windows10下面用VMware建了一个虚拟机进行试验。
开发环境:VMWare虚拟机 Ubuntu 18.04
Linux源码版本:linux4.9.229

学习笔记 模块

Linux是“单块内核”(monolithic)的操作系统,即整个系统内核都运行在一个单独的保护域中,但其内核时模块化组成在,在运行的过程中可以动态的向其中插入或从中删除代码。这些代码(包括相关的子例程、数据、函数入口和函数出口)被一并组合在一个单独的二进制镜像中(即后文中的Makefile文件中的obj-m := helloDev.o这一个操作也就是.ko文件),这个就叫做模块。

字符设备驱动的代码

首先,UP主给出了最简单的一个字符设备驱动的代码,具体代码如下,学习过程中的笔记就当写注释了

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_MAX    (10)
#define OK            (0)
#define ERROR         (-1)

struct cdev *gDev;
struct file_operations *gFile;
dev_t  devNum;
unsigned int subDevNum = 1;
int reg_major  =  232;    
int reg_minor =   0;
char *buffer;
int flag = 0;
int hello_open(struct inode *p, struct file *f)
{
    printk(KERN_EMERG"hello_openrn");
    return 0;
}

ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
    printk(KERN_EMERG"hello_writern");
    return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
    printk(KERN_EMERG"hello_readrn");      
    return 0;
}
int hello_init(void)
{
    
    devNum = MKDEV(reg_major, reg_minor);
    //根据主次设备号生成一个devNum,具体实现是将主设备号作一个偏移,然后将其或上次设备号
    //主次设备号用来唯一标识一个设备,主:标识一类设备 次:该类设备下的不同设备
    if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){ //register_chrdev_region是将设备注册到内核里面
    //将设备号注册到内核里面后,该设备号其他人就无法再注册
        printk(KERN_EMERG"register_chrdev_region ok n"); 
    }else {
    printk(KERN_EMERG"register_chrdev_region error n");
        return ERROR;
    }
    printk(KERN_EMERG" hello driver init n");
    gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
    //声明一个file_operation类型的变量gFile
    //struct file_operations结构体里面声明了许多对文件操作的函数
    gFile->open = hello_open;
    gFile->read = hello_read;
    gFile->write = hello_write;
    //对gFile中的三个操作进行指向,最终由应用层请求进行调用
    gFile->owner = THIS_MODULE;
    cdev_init(gDev, gFile);
    //建立了gDev和gFile之间的联系
    //注:在学习内核的过程中,有些函数特别复杂,我们可以不去细究。类似这样的,我们知道这个函数将两者建立了联系即可
    cdev_add(gDev, devNum, 3);
    //建立gDev和设备号之间的联系
  	//至此,建立了设备号与gDev与gFile的联系
    return 0;
}

void __exit hello_exit(void) //驱动卸载函数
{
    cdev_del(gDev); //与cdev_add操作对应
    unregister_chrdev_region(devNum, subDevNum);//与register_chrdev_region操作对应
    return;
}
module_init(hello_init); //声明驱动的入口函数是hello_init
module_exit(hello_exit); //声明驱动的出口函数是hello_exit
MODULE_LICENSE("GPL");	//声明了版权

学习笔记

  • 字符设备通常缩写为cdev,它是不可寻址的,仅提供数据的流式访问,就是一个个字符或者一个个字节。
  • hello_init()就是这一个模块的入口点,它通过module_init()注册到系统中,在内核装载到时候被调用。而module_init()实际上不是一个函数调用,而是一个宏调用。唯一的参数就是模块的初始化函数。模块的所有初始化函数必须满足int my_init(void)这样的格式
  • hello_exit()是模块的出口函数,由module_exit()注册到系统中,当模块从内存卸载的时候,便会调用此函数。即对这个模块的清理工作,其退出函数必须负荷void my_exit(void)格式
  • 由于init和exit函数通常不会被外部函数直接调用,因此我们不必导出该函数,因此这两个函数都可以标记为static类型
字符设备驱动的Makefile(构建模块)
ifneq ($(KERNELRELEASE),) 
obj-m := helloDev.o #给内核的编译系统识别,内核系统会将所有obj-m的二进制文件变为驱动文件
#此处执行就是将helloDev.c生成为helloDev.o文件,再将其编译为驱动文件即.ko文件
else
PWD := $(shell pwd) #得到当前目录
#KDIR:= /lib/modules/4.4.0-31-generic/build #自己编译的一个内核下的驱动,插入编译的这个内核的驱动中
KDIR := /lib/modules/`uname -r`/build #当前运行的Ubuntu的系统所在的地方,插入到自己系统的驱动中
all:
	make -C $(KDIR) M=$(PWD)
	#make -C $(KDIR)表示进入内核目录,并执行其中的Makefile文件
	#M=$(PWD) 表示执行完了之后返回到当前的目录之下继续读入、执行当前的Makefile文件
clean:	
	rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif

笔记

  1. 首先是最简单ifneq的使用:ifneq($(变量名), 变量值),ifneq是为了比较两个参数是否不相同,不相等为true,相等为false
    第二个变量是NULL即为空。即如果变量为空,则为false,进入else语句

补充一点 Makefile笔记
ifeq 判断参数是否不相等,相等为 true,不相等为 false。
ifneq 判断参数是否不相等,不相等为 true,相等为 false。
ifdef 判断是否有值,有值为 true,没有值为 false。
ifndef 判断是否有值,没有值为 true,有值为 false。

  1. 在执行这个Makefile文件的时候,如果使用的make指令,那么Makefile文件就会执行all:后面的语句,即上方的make -C $(KDIR) M=$(PWD),如果使用make clean指令,则会执行clean:后面的语句
  2. 而这个文件最关键的是会执行两次该Makefile文件,原因如下:
    当第一次执行ifneq ($(KERNELRELEASE),) 时,此时的KERNELRELEASE变量还没有被定义,因此此时判断为fasle进入else后面的执行语句,然后进入all:后面的语句,all:后面的语句执行过程如下
    make -C $(KDIR)表示进入内核目录,并执行其中的Makefile文件,此时,KERNELRELEASE变量已被定义,不再为空
    M=$(PWD) 表示执行完了之后返回到当前的目录之下继续读入、执行当前的Makefile文件
    再次执行当前Makefile文件之后,ifneq判断为true,执行obj-m := helloDev.o
管理模块的位置

在前面的这些知识里面我们可以知道,.ko文件就是一个文件镜像,Linux内核系统就是把这样的一个镜像文件插入到运行的内核当中。而这里面有一个重要的点是,我们在哪里管理我们的模块,也就是把.ko文件放在哪?

  1. 放到内核源代码树种
    在前面Linux内核学习(一)里面我们知道,在Linux内核代码里面设备驱动程序有一个专门的目录即/drivers,在内部有许多的子目录,而我们也可以将我们自己编写的设备驱动模块放在其内部。这里面有drivers/char(存放字符设备),/drivers/usb(存放USB设备)

    注:这个规矩并不是墨守成规的,许多usb设备也属于字符设备,但这样的目录规则有助于我们理解各个设备的关系

    但是我们如果将我们的文件放到这里面的目录之下,该目录下会同时存在大量的C源代码文件和其他文件目录,不便于自己编写。
    因此,我们也可以自己在/drivers/char下创建一个自己的一个目录,例如/drivers/char/helloDev,如果是这样的话,我们就需要在drivers/char/Makefile里面加入一行obj-m += helloDev/,意思是编译模块的时候,要进入helloDev目录。然后我们需要在drivers/char/helloDev里面加入一个Makefile文件,并包含obj-m += helloDev.o(即将helloDev.c编译为helloDev.ko文件)

  2. 放在内核代码之外
    我们也可以将其放在一个自己的文件夹里面,来进行维护,那么就只需要在你当前的目录之下建立一个Makefile文件,里面包含
    obj-m :=helloDev.o,这样就把文件编译为.ko文件了,当然,如果你有多个元件文件需要编译到helloDev.o里面,我们就需要加入helloDev-objs := helloDev.o goodbye.o,这样helloDev.c和goodbye.c就被编译到helloDev.ko里面了
    当然我们也要让内核知道我们要编译的模块在哪
    可以选择在Makefile里面加入make -C $(KDIR) M=$(PWD)例如前面的文件种
    或者使用make指令的时候使用make -C /kernel/source/location SUBDIRS=$PWD modules

驱动的插入和卸载

有了上述的准备工作之后,我们就可以将我们的helloDev.ko驱动加入到我们的Linux进程当中啦
首先,我们先把我们的内核日志中的所有内容清理一下,便于我们查看后续插入我们自己的驱动而输出的信息

sudo dmesg -C #需要在root权限下清理日志
dmesg # 查看日志内容 

当dmesg没有输出内容之后,代表已经清理完了
输入sudo insmod helloDev.ko将我们的设备驱动加入到Linux内核当中
然后我们输入lsmod查看我们是否加入成功

发现已经存在了,代表已经插入成功
这个时候,我们再使用dmesg 查看我们插入驱动的输出信息,发现确实已经输出了

当我们想要把这个设备卸载的时候,我们使用sudo rmmod helloDev即可以把驱动卸载,卸载之后,再用lsmod就看不到啦

但这两个指令并不只能,先进工具modprobe更智能,它还会自动加载任何安装的模块及任何它所依赖的模块
插入模块指令modprobe module [ module parameters] 卸载模块指令modprobe -r modules,都需要在root权限下运行

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/826736.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号