韦东山Linux驱动入门实验班(2)hello驱动---驱动层与应用层通讯,以及自动产生设备节点

简介: 韦东山Linux驱动入门实验班(2)hello驱动---驱动层与应用层通讯,以及自动产生设备节点

前言

(1)学习韦东山老师的Linux,因为他讲的很精简,以至于很多人听不懂。接下来我讲介绍韦东山老师的驱动实验班的第二个Hello程序。


(2)注意,请先学习完视频再来看这个教程!本文仅供入门学习!如需深入,请搜索其他博客!


(3)因为上一个教程已经讲的很详细了,所以很多内容不会重复讲解,只会说明区别的地方。所以阅读本教程之前建议先阅读:韦东山Linux驱动入门实验班(1)hello驱动


(4)gitee仓库;GitHub仓库;


本教程与上一个教程区别

这一篇中的应用层代码和上一篇是一模一样的。所以本文的所有代码都是针对驱动层的!如果应用层代码不理解,可以看上一篇博客!


区别1---驱动层与应用层数据交互

区别讲解

(1)


<1>我们在阅读编写上一个程序的时候,有没有发现一个问题。就是说,我们无论给驱动程序写入什么,最终读取出来的都是一个奇怪的符号。这个是为什么呢?


<2>因为,应用层和驱动层是完全隔绝开来的,应用层无法直接访问驱动层中的各种变量。因为这个操作系统的是要给很多开发者使用的,而这些开发者,可能有些人水平不够,或者是想恶意破坏。那么就会存在一个问题,如果这些人给驱动程序写入了一些错误数据,就容易让整个系统崩溃。所以驱动层和应用层是隔绝的,应用层无法直接访问驱动层,同理,驱动层也无法直接访问应用层。


<3>驱动层和应用层是怎么隔离的呢?首先,应用层发出请求,想访问驱动层。这个时候这条指令将会传递给CPU,CPU读取指令之后将信息发给MMU(内存管理单元)。这个时候,MMU将会发现,应用层发起请求,想访问驱动层,于是MMU将会把这个请求杀死。


<4>应用层和驱动层是隔离的。那么如果我硬要驱动层跟应用层进行交互,怎么办呢?于是就可以使用两个函数:copy_from_user(驱动层得到应用层数据);copy_to_user(驱动层发数据给应用层)


(3)应用层和驱动层有交互函数。那么驱动层怎么跟硬件交互呢?在我们编写单片机程序的时候,可以直接使用寄存器编写程序,也可以使用官方提供的库函数。而Linux中,我们也是两种办法,第一种是使用寄存器直接控制,第二种就是使用子系统函数。每个寄存器都有一个地址,我们将这个称之为物理地址。但是我们的驱动程序不能直接使用物理地址,他必须是使用ioremap函数,返回一个虚拟地址,用指针存放。之后,我们就可以通过这个指针操作寄存器了。


代码实操

(1)咱们这里有两个函数都有改动,分成两部分讲。


(2)hello_read():


<1>unsigned long len = size > 100 ? 100 : size;


这一部分用于判断传入驱动层给应用层发送的字符是否大于100个,如果大于100个字符,那么就只发送100个字符。如果驱动层给应用层发送的字符小于100个,那么就发送实际数量的字符数。


<2>copy_to_user(buf, hello_buf, len);


1,我们copy_to_user()函数咱们上面介绍了,是驱动层用于给应用层发送数据的函数。第一个参数是最终发送到应用层的数据,因为我们hello_read()函数的第二个参数是buf,所以copy_to_user()第一个参数传入buf。


2,copy_to_user()的第二个参数是驱动层需要发送给应用层的数据,这个是需要我们自己在驱动层创建无符号字符数组hello_buf[]。hello_buf[]这个数组负责存放驱动层和应用层的交互信息。


3,copy_to_user()的第三个参数是需要传输的字符数量。如果传输的字符大于100,那么就只传输100个字符。如果传输的字符小于100个,那么就按照需要传输的字符数量传输。


<3> return len; 最终返回传输的字符数量。与原来的 return size;不同在于, return size;是没有加上最多传输100个字符的限制。


(3)hello_write():


<1>与hello_read()的一样变化。


<2>copy_from_user(hello_buf, buf, len);


1,copy_from_user()函数和copy_to_user()函数使用方法类似的。只不过copy_from_user()函数是用于应用层给驱动层发送信号。


2,copy_from_user()的第一个参数是目的地址,所以需要传入hello_buf。第二个参数是起始地址,所以需要传入buf。第三个参数与copy_to_user()一样。


<3>最终返回值的改变与hello_read()的一样变化。


(4)最后强调一下,这个代码会有两个警告。ignoring return value of ‘copy_from_user’, declared with attribute warn_unused_result和 ignoring return value of ‘copy_to_user’, declared with attribute warn_unused_result。这两个警告翻译过来就是忽略使用warn_unused_result属性声明的' copy_from_user '的返回值 和 忽略使用warn_unused_result属性声明的' copy_to_user '的返回值。


这两个警告是什么意思呢?有什么影响?很简单copy_from_user和copy_to_user是存在返回值的。而我们没有接收这两个函数的返回值,所以会存在警告,无需理会。

static unsigned char hello_buf[100]; //存放驱动层和应用层交互的信息
/*
 *传入参数 :
   *filp :要读的文件
   *buf :读的数据放在哪里
   *size :读多大数据
   *offset :偏移值(一般不用)
 *返回参数 :读到的数据长度 
*/
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
  //判断size是否大于100,如果大于100,len=100,否则len=size
    unsigned long len = size > 100 ? 100 : size;
  /*__FILE__ :表示文件
   *__FUNCTION__ :当前函数名
   *__LINE__ :在文件的哪一行
  */
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  /* 作用 : 驱动层发数据给应用层
   * buf  : 应用层数据
   * hello_buf : 驱动层数据
   * len  :数据长度
  */
    copy_to_user(buf, hello_buf, len);
    return len;
}
/*
 *传入参数 :
   *filp :要写的文件
   *buf :写入的数据来自于buf
   *size :写多大数据
   *offset :偏移值(一般不用)
 *返回参数 :写的数据长度  
*/
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
  //判断size是否大于100,如果大于100,len=100,否则len=size
    unsigned long len = size > 100 ? 100 : size;
  /*__FILE__ :表示文件
   *__FUNCTION__ :当前函数名
   *__LINE__ :在文件的哪一行
  */
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  /* 作用 : 驱动层得到应用层数据
   * buf  : 应用层数据
   * hello_buf : 驱动层数据
   * len  :数据长度
  */
    copy_from_user(hello_buf, buf, len);
    return len;
}

结果演示

(1)与上一个博客一样流程:


<1>Ubuntu下make编译


<2>打开开发板,输入mount -t nfs -o nolock,vers=3  192.168.5.11:/home/book/nfs_rootfs  /mnt共享文件


<3>内核打印可以选中打开或者不打开:


关闭内核打印:echo "0 4 0 7" > /proc/sys/kernel/printk

打开内核打印:echo "7 4 1 7" > /proc/sys/kernel/printk


<4>insmod hello_drv.ko装载驱动


<5>cat /proc/devices:查看当前已经被使用掉的设备号(注意,这里的设备号之后的创建设备节点需要用到)


<6> mknod  /dev/xyz c 240 0 (xyz可以为任意名字,设备号与上面的有关,0表示次设备号,随便填)


<7>在应用层调用设备节点,如./hello_test /dev/xyz  123


<8>卸载驱动程序: rmmod 驱动程序


<9>删除设备节点:rm  /dev/xyz


(2) 关闭内核打印演示结果

(3)打开内核打印演示结果


区别二---自动创建设备节点

讲解

(1)咱们有没有发现一个问题,驱动装载流程太长了。我们能不能自动创建一个设备节点,然后自己删除呢?答案是可以的,不然我也不会提及。


(2)如何在让系统实现自动创建设备节点呢?这个时候我们就要讲到两个函数。class_create()和device_create()。


<1>class_create()函数用于动态创建设备的逻辑类,此函数的执行效果就是在目录/sys/class下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。同时在/sys/devices目录下也会创建新的空文件夹。


<2>device_create()函数就是用于创建设备节点的,第一个参数为class_create()的返回值。class_create()函数不是在/sys/class中创建一个空的文件夹麻,device_create()函数就是在这个空的文件夹中创建一个链接文件。同时在/sys/devices/virtual目录下创建新的逻辑设备目录。这个链接文件,也就是咱们输入mknod命令创建的设备节点。


(3)自动创建了设备节点之后,我们还需要销毁设备节点。那么这个时候又需要使用两个函数了。device_destroy()和class_destroy()。


<1>因为我们创建设备节点的时候,是先注册驱动,再创建逻辑类,最后创建设备节点的。所以销毁的时候需要反过来。先销毁设备节点,再销毁逻辑类,最后卸载驱动。


<2>class_destroy()执行的效果是删除函数__class_create()或宏class_create()在目录/sys/class下创建的逻辑类对应的文件夹。device_destroy()用于销毁设备节点。


代码

//在命令行输入insmod命令,就是注册驱动程序。之后就会进入这个入口函数
//3,入口函数
static int hello_init(void)
{
  /*将hello_drv这个驱动放在内核的第n项,中间传入的名字不重要,第三个是要告诉内核的驱动
   *因为我们不知道第n项是否已经存放了其他驱动,就可以放在第0项,然后让系统自动往后遍历存放到空的地方
   *major为最终存放的第n项,等下卸载程序需要使用。如果不卸载程序,可以不管这个
  */
    major = register_chrdev(0, "100ask_hello", &hello_drv);
  //如果成功注册驱动,打印
  printk("insmod success!\n");
  /******这里相当于命令行输入 mknod  /dev/hello c 240 0 创建设备节点*****/
  //创建类,为THIS_MODULE模块创建一个类,这个类叫做hello_class
  hello_class = class_create(THIS_MODULE, "hello_class");
  if (IS_ERR(hello_class))  //如果返回错误
  {
    //打印类创建失败
    printk("failed to allocate class\n");
    //返回错误
    return PTR_ERR(hello_class);
  }
  /*在hello_class类下面创建设备
   *无父设备的指针
   *MKDEV传入主设备号major和此设备号0
   *没有私有数据
   *输入参数是逻辑设备的设备名,即在目录/dev目录下创建的设备名
  */
    device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  /* /dev/hello */
   return 0;
}
//在命令行输入rmmod命令,就是注册驱动程序。之后就会进入这个出口函数
//4,出口函数
static void hello_exit(void)
{
  //销毁hello_class类下面的设备节点
    device_destroy(hello_class, MKDEV(major, 0));
  //销毁hello_class类
    class_destroy(hello_class);
  //卸载驱动程序
  //第一个参数是主设备号,第二个是名字
    unregister_chrdev(major, "100ask_hello");
  //如果成功卸载驱动,打印
  printk("rmmod success!\n");
}


效果演示

(1)演示自动产生设备节点

(2)现在使用驱动需要的步骤:


<1>Ubuntu下make编译


<2>打开开发板,输入mount -t nfs -o nolock,vers=3  192.168.5.11:/home/book/nfs_rootfs  /mnt共享文件


<3>内核打印可以选中打开或者不打开:


关闭内核打印:echo "0 4 0 7" > /proc/sys/kernel/printk

打开内核打印:echo "7 4 1 7" > /proc/sys/kernel/printk


<4>insmod hello_drv.ko装载驱动


<5>在应用层调用设备节点,如./hello_test /dev/xyz  123


<6>卸载驱动程序: rmmod 驱动程序


目录
相关文章
|
27天前
|
消息中间件 Java Kafka
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
本文介绍了Kafka集群的搭建过程,涵盖从虚拟机安装到集群测试的详细步骤。首先规划了集群架构,包括三台Kafka Broker节点,并说明了分布式环境下的服务进程配置。接着,通过VMware导入模板机并克隆出三台虚拟机(kafka-broker1、kafka-broker2、kafka-broker3),分别设置IP地址和主机名。随后,依次安装JDK、ZooKeeper和Kafka,并配置相应的环境变量与启动脚本,确保各组件能正常运行。最后,通过编写启停脚本简化集群的操作流程,并对集群进行测试,验证其功能完整性。整个过程强调了自动化脚本的应用,提高了部署效率。
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
|
1月前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
73 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
2月前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
3月前
|
Linux 数据安全/隐私保护 索引
linux inode索引节点使用率100% 解决+hustoj忘记密码+最新MDK注册方法
linux inode索引节点使用率100% 解决+hustoj忘记密码+最新MDK注册方法
59 1
|
5月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
65 3
|
5月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
42 1
|
5月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
63 1
|
5月前
|
Ubuntu Linux
内核实验(四):Qemu调试Linux内核,实现NFS挂载
本文介绍了在Qemu虚拟机中配置NFS挂载的过程,包括服务端的NFS服务器安装、配置和启动,客户端的DHCP脚本添加和开机脚本修改,以及在Qemu中挂载NFS、测试连通性和解决挂载失败的方法。
329 0
内核实验(四):Qemu调试Linux内核,实现NFS挂载
|
5月前
|
NoSQL Linux Android开发
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
本文介绍了如何在QEMU中挂载虚拟分区、创建和编译简单的Linux内核模块,并在QEMU虚拟机中加载和测试这些内核模块,包括创建虚拟分区、编写内核模块代码、编译、部署以及在QEMU中的加载和测试过程。
282 0
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
|
5月前
|
Linux 网络安全 开发工具
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu
本文介绍了如何基于Linux Kernel 5.15.102版本和BusyBox创建一个自定义的迷你Linux ARM系统,并使用QEMU进行启动和调试,包括内核和BusyBox的编译配置、根文件系统的制作以及运行QEMU时的命令和参数设置。
482 0
内核实验(二):自定义一个迷你Linux ARM系统,基于Kernel v5.15.102, Busybox,Qemu