目录

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

写在前面

之前做项目的时候,有前辈告诉自己,要去学一下Linux内核,对很多方面都有帮助,现在闲下来,来花时间学一下这一部分的知识点,也算是一个学习笔记
目前跟着B站UP主——简说linux 的教程《Linux内核开发100讲》学习,链接如下:
简说linux个人空间
本章参考学习的链接如下:
Makefile中($(KERNELRELEASE),)执行分析
《Linux内核设计与实现》
在学习的过程中,我也会对遇到的各种问题进行深一步学习, 从而总结知识点到博客当中,这就会出现内容可能会四处跳跃,但是这种跳跃符合我的学习过程。

整体环境

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

学习笔记

设备

在Linux中,大部分设备驱动就是表示物理设备,只有一些设备驱动是虚拟的,仅提供访问内核功能。因此大部分设备驱动就是调用实际中的设备所提供的代码

模块

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

因此给Linux内核加入新的设备的过程大致如下

#mermaid-svg-PhdkVKa6JNVSJc8H {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .error-icon{fill:#552222;}#mermaid-svg-PhdkVKa6JNVSJc8H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PhdkVKa6JNVSJc8H .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-PhdkVKa6JNVSJc8H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PhdkVKa6JNVSJc8H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PhdkVKa6JNVSJc8H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PhdkVKa6JNVSJc8H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PhdkVKa6JNVSJc8H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PhdkVKa6JNVSJc8H .marker.cross{stroke:#333333;}#mermaid-svg-PhdkVKa6JNVSJc8H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PhdkVKa6JNVSJc8H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .cluster-label text{fill:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .cluster-label span{color:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .label text,#mermaid-svg-PhdkVKa6JNVSJc8H span{fill:#333;color:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .node rect,#mermaid-svg-PhdkVKa6JNVSJc8H .node circle,#mermaid-svg-PhdkVKa6JNVSJc8H .node ellipse,#mermaid-svg-PhdkVKa6JNVSJc8H .node polygon,#mermaid-svg-PhdkVKa6JNVSJc8H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PhdkVKa6JNVSJc8H .node .label{text-align:center;}#mermaid-svg-PhdkVKa6JNVSJc8H .node.clickable{cursor:pointer;}#mermaid-svg-PhdkVKa6JNVSJc8H .arrowheadPath{fill:#333333;}#mermaid-svg-PhdkVKa6JNVSJc8H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PhdkVKa6JNVSJc8H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PhdkVKa6JNVSJc8H .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-PhdkVKa6JNVSJc8H .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-PhdkVKa6JNVSJc8H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PhdkVKa6JNVSJc8H .cluster text{fill:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H .cluster span{color:#333;}#mermaid-svg-PhdkVKa6JNVSJc8H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PhdkVKa6JNVSJc8H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

设备驱动代码
编译为模块
插入到内核当中

字符设备驱动的代码

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

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>#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_open\r\n");return 0;
}ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{printk(KERN_EMERG"hello_write\r\n");return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{printk(KERN_EMERG"hello_read\r\n");      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权限下运行

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

  1. 编写最简单的字符设备驱动

    编写最简单的字符设备驱动 1 编写驱动代码 2 编写makefile 3 编译和加载驱动 4 编写应用程序测试驱动 参考文章: linux驱动开发第1讲:带你编写一个最简单的字符设备驱动 linux驱 ...

  2. 从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响(一)

    从 2.4 到 2.6:Linux 内核可装载模 块机制的改变对设备驱动的影响 <?xml:namespace prefix = o ns = "urn:schemas-microso ...

  3. Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动

    现在,我们来编写自己第一个字符设备驱动 -- 点亮LED.(不完善,后面再完善) 硬件平台:Exynos4412(FS4412) 编写驱动分下面几步: a -- 查看原理图.数据手册,了解设备的操作方 ...

  4. linux open函数_Linux驱动开发 / 字符设备驱动内幕 (1)

    哈喽,我是老吴,继续记录我的学习心得. 一.保持专注的几个技巧 将最重要的事放在早上做. 待在无干扰环境下,比如图书馆. 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象. 让"开 ...

  5. Linux之字符设备驱动框架

    目录 一.驱动介绍 1.内核模块 2.日志级别 3.模块符号的导出 4.内核模块参数 二.字符设备驱动(一) 1.模块加载 2.注册字符设备驱动 3.内存映射 三.字符设备驱动(二) 1.模块加载 2 ...

  6. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  7. Android 尝试写一份Linux 字符设备驱动

    从事android工作几年时间,功底不是很深,一直围绕这android系统定制移植开发,慢慢的从应用层接触到framework层,在接触到hal,目前从事的工作wifi驱动相关工作.但是没有系统的学习 ...

  8. linux驱动设备开发1——字符设备驱动

    0 LInux内核 linux内核的内部结构:内核各个模块如下所示 linux的驱动只有三种类型:字符设备驱动(键盘,鼠标).块设备驱动(硬盘).网络设备驱动(网卡,can等) 驱动的静态加载和动态加 ...

  9. Linux内核学习-字符设备驱动学习(二)

    在Linux内核学习-字符设备驱动学习(一)中编写字符设备驱动的一种方法,但是需要手动创建设备节点. 有没有能够自动的创建设备节点的呢? 有!使用class_create()和device_creat ...

  10. 我的Linux内核学习笔记

    在开始今天的内容之前,其实有一些题外话可以和大家分享一下.自从工作以来,我个人一直都有一个观点.那就是怎么样利用简单的代码来说明开发中的问题,或者是解释软件中的原理,这是一个很高的学问.有些道理看上去 ...

最新文章

  1. mysql 死锁原因_Mysql并发时经典常见的死锁原因及解决方法
  2. printf linux 头文件,Linux C 格式化输出时要注意的问题
  3. php可以做门禁卡系统吗_PHP研发工程师入门篇:论PHP可以做什么?
  4. 公司为什么宁愿花11K月薪招新人,也不愿意花9K的月薪留住老员工?
  5. 设计模式之组合模式(Composite Pattern)
  6. JVM GC(垃圾回收机制)
  7. linux 系统命令之wget和yum和apt-get区别
  8. 寻找链表中值域最小的节点并移到链表的最前面
  9. 学习强化学习无法避开的两个词:Model-Based与Model-Free
  10. 得到照片_用PS制作重曝效果的人像艺术照片
  11. 高清版计算机组成原理(第2版)-唐朔飞
  12. 微信测试拉黑的软件,如何检测微信里有没有人把你拉黑?教你一招!
  13. Yandex插件使用说明——Slager_Z
  14. 智慧工厂 VR 拆解零件 —— Hightopo 3D 虚实现实可视化系统
  15. Dart语言详解(一)——详细介绍
  16. Springboot+vue项目火车订票管理系统
  17. 电脑开热点手机连不上
  18. 宝德服务器——企业需要真正的按需定制产品
  19. Apple iBeacons
  20. iOS 阿里云上传图片

热门文章

  1. springboot权限管理系统
  2. 车路协同应用场景分析
  3. Python的伪造数据生成器:Faker
  4. 树莓派搭建全功能NAS服务器(07):管理你的书库随心阅读
  5. Myeclipse10破解版安装包
  6. 朴素贝叶斯模型进行垃圾邮件分类
  7. python发邮件被认定为垃圾邮件_【python文本分类】20行代码识别垃圾邮件
  8. 驱动ST7789 240*240 TFT屏 制作分光棱镜显示要点总结(镜像后图片颜色R、B对调了,使用PS修改图片)
  9. linux清除所有后台程序,Linux查看和关闭后台运行程序的方法
  10. python软件工程师简历范文_嵌入式软件工程师完整简历范文