linux驱动的学习 & 驱动开发初识

这篇具有很好参考价值的文章主要介绍了linux驱动的学习 & 驱动开发初识。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 设备的概念

在学习驱动和其开发之前,首先要知道所谓驱动,其对象就是设备

1.1 主设备号&次设备号:

在Linux中,各种设备都以文件的形式存在/dev目录下,称为设备文件最上层的应用程序可以打开,关闭,读写这些设备文件,从而完成对设备的操作

为了管理这些设备,系统为设备编了号,每个设备都拥有主设备号次设备号主设备号用于区分不同种类的设备,而次设备号用于区分同一类型的多个设备(对于常用的设备如硬盘,Linux赋予的主设备号一般是3)

  • 在/dev目录下输入“ls -l”,就可以看到设备文件对应的主次设备号

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

1.2 设备号的用处

在了解了什么是主次设备号之后,就要了解设备号的用处:

  • 用户态中:当用户调用了如open, read, write等函数想要操作设备文件时,需要两个参数,第一个是文件名,第二个就是设备号
  • 内核态中:存在着一个驱动链表,用于管理所有设备的驱动,而驱动在链表中的位置就由设备号来检索

2 驱动的概念

参考:最全Linux驱动开发全流程详细解析(持续更新)-CSDN博客

Q1:什么是驱动?

A:驱动与底层硬件直接打交道,充当了硬件(设备)与应用软件中间的桥梁


Q2:驱动的功能?

  1. 对设备初始化和释放
  2. 把数据从内核传送到硬件 & 从硬件读取数据
  3. 读取应用程序传送给设备文件的数据 & 回送应用程序请求的数据
  4. 检测和处理设备出现的错误

Q3:驱动的分类?

Linux驱动分为三个基础大类:字符设备驱动块设备驱动网络设备驱动

  • 字符设备(Char Device)
  1. 字符设备是个能够像字节流(类似文件)一样被访问的设备
  2. 对字符设备发出读/写请求时,实际的硬件I/O操作一般紧接着发生
  3. 字符设备驱动程序通常至少要实现open、close、read和write系统调用
  4. 比如我们常见的lcd、触摸屏、键盘、led、串口等等,他们一般对应具体的硬件都是进行出具的采集、处理、传输
  • 块设备(Block Device)
  1. 一个块设备驱动程序主要通过传输固定大小的数据(一般为512或1k)来访问设备
  2. 块设备通过buffer cache(内存缓冲区)访问,可以随机存取,即:任何块都可以读写,不必考虑它在设备的什么地方
  3. 块设备可以通过它们的设备特殊文件访问,但是更常见的是通过文件系统进行访问
  4. 只有一个块设备可以支持一个安装的文件系统
  5. 比如我们常见的电脑硬盘、SD卡、U盘、光盘等
  • 网络设备(Net Device)
  1. 任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备
  2. 访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点
  3. 内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函(socket函数)而不是read、write等
  4. 比如我们常见的网卡设备、蓝牙设备

回顾上节Linux系统的构造:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

可见,驱动位于内核态,上面是系统调用;下面是硬件(设备)

2.1 上层用户操控设备的流程:

每一个系统调用,在驱动中都有与之对应的一个驱动函数

以open为例:其系统调用为sys_open,那么驱动文件中就会有一个与其对应的“实现open的驱动函数:xxx_open()”,其大致步骤就是:用户层C库的open系统调用层的open驱动函数的open

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

以上是一个我作为初学者总结的大概流程,下图是一个更详细的流程:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

2.2 Linux驱动的运行方式

  1. 驱动编译进 Linux 内核中,当 Linux 内核启动的时就会自动运行驱动程序
  2. 驱动编译成模块(Linux 下模块扩展名为.ko),并在Linux 内核启动以后使用相应命令加载驱动模块

3 驱动开发实战

尝试使用“将驱动编译成模块的方式”来编写“字符设备的驱动

驱动开发不是一件容易的事情,对于初学者,可以先根据一个固定的框架来学习驱动的开发:

3.1 基本的字符设备驱动框架

注释很重要,认真看

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名
 
 
//_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
     
    return 0;
}
 
//_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_write\n");  //内核的打印函数和printf类似

    return 0;
}
 
static struct file_operations pin4_fops = { //结构体的类型是“file_operations”,名字可以自定义
//该结构体的成员就包含实现open和write的驱动函数
//当上层用户想要open或者write这个设备时,就会最终跳转到这个驱动代码中实现的open和write操作函数
//此处只赋值了该结构体中的三个成员变量(在keil中是不能这样写的,linux中可以),这个结构体其实有很多成员,如果想要实现更多的驱动函数,可以把更多的该结构体成员赋值并在这段代码中重写
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};
 
int __init pin4_drv_init(void)   //真实驱动入口
{
 
    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name, &pin4_fops);  //注册驱动,告诉内核:把这个驱动加入到内核驱动的链表中

    //以下两句代码目的是“生成设备文件”,也可以通过“mknod”命令手动生成,但是一般不会这样做
    pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //先创建‘类’
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //再创建‘设备’
 
    return 0;
}
 
void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class,devno); //先销毁‘设备’
    class_destroy(pin4_class); //在销毁‘类’
    unregister_chrdev(major, module_name);  //卸载驱动
}
 
module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");    //linux内核遵循GPL协议

在代码中添加大量“static”的原因是:内核的代码太多了,为了防止出现重名导致歧义,加入static可以保证变量的作用域只在当前代码中,从而不会影响到其他的代码。

file_operation 结构体是 Linux 内核驱动操作函数集合

比如,我在写某设备的驱动文件,并想为其实现一个read的驱动函数,那么我就需要:

  1. 定义一个类型为file_operation的结构体XXX,其中XXX是自定义的结构体名
  2. 在XXX结构体中使用“.read = XXX_read,”,其中XXX_read是自定义的函数名
  3. 在驱动代码里重写XXX_read函数,函数的参数格式和file_operation类型结构体中给出的write成员的参数格式保持一致,函数体就是想要实现的具体内容

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

3.2 驱动的编译

  • 打开虚拟机,进入Linux源码的路径:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

  • 然后进入“drivers/char/”子目录(driver:驱动;char:字符型设备):

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

  • 在这个路径下创建一个新的C文件"mydriver_pin4.c",内容为刚刚的字符驱动框架:
linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

  • 修改当前路径下的Makefile,确保这个新的驱动会被编译到:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

  • 回到linux内核源码的路径,运行以下指令尝试编译:
ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules

回顾在前几节学习内核编译时的代码:

ARCH=arm CROSS_COMPILE=/home/mjm/ras_CrossCompile/gcc-linaro-5.1-2015.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- KERNEL=kernel7 make -j4 zImage modules dtbs

区别就是:代码的最后少了“zImage”和“dtbs”。原因也很简单,现在只需要编译驱动,不需要再次生成镜像文件等其他文件。

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

  • 将编译好的“mydriver_pin4.ko”通过以下的scp命令发送到树莓派:
scp drivers/char/mydriver_pin4.ko pi@192.168.2.26:/home/pi/mjm_code

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

 此时,编译好的驱动文件就出现在树莓派上了:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

3.3 驱动的加载(卸载)

由于现在刚刚把驱动编译成了.ko的模块,所以需要运行以下指令来加载驱动模块:

sudo insmod mydriver_pin4.ko

补充:

  • 驱动的卸载:
sudo rmmod mydriver_pin4.ko //此时驱动名字后不用加".ko"
  • 查看内核模块:
lsmod

 运行成功后,就可以在/dev下看到生成的设备文件“pin4”了:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

如果用“ls -l” ,就可以看到这个设备文件的主次设备号,和框架代码中的设置相同:

 linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

3.4 驱动的测试

在树莓派下写一个测试驱动的C代码:

pin4_test.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main()
{
	int driver_fd;

	driver_fd = open("/dev/pin4",O_RDWR); //以可读可写打开的方式打开驱动
	if(driver_fd < 0){
		perror("fail to open driver file:");
	}else{
		printf("open driver file success!\n");
	}

	driver_fd = write(driver_fd,'a',1); //向驱动文件写一个字节
	if(driver_fd < 0){
		perror("fail to write to driver file:");
	}else{
		printf("write success!\n");
	}

	return 0;
}

编译然后运行:

1. gcc pin4_test.c -o pin4_test
2. sudo ./pin4_test

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

如果加了sudo还是没法运行,可以根据错误提示修改,可能需要给驱动文件一个执行权限:

sudo chmod 666 /dev/pin4
//666代表让所有用户都有所有权限

 可见,运行成功,没有报错!此时还可以另开一个窗口输入“dmesg”查看内核打印的信息:

linux驱动的学习 & 驱动开发初识,linux,驱动开发,linux驱动,设备,Linux系统结构,系统架构

 可见内核也按照框架代码中的printk成功打印了信息!驱动测试成功!

同时,结果也再次印证了:当用户在最上层对 驱动文件 调用C库的open函数后,最后的结果还是调用最底层 驱动文件里实现的open驱动函数文章来源地址https://www.toymoban.com/news/detail-764400.html

到了这里,关于linux驱动的学习 & 驱动开发初识的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【IMX6ULL驱动开发学习】12.Linux驱动之设备树

    承接上一篇博客 【IMX6ULL驱动开发学习】11.驱动设计之面向对象_分层思想(学习设备树过渡部分) 代码获取: https://gitee.com/chenshao777/imx6-ull_-drivers 我后面将三个层合并了(实际上只有前两层),合并成一个dev_drv.c了,暂时没有加GPIO操作,只是个框架 合并前的代码在 11.butt

    2024年02月13日
    浏览(35)
  • 【嵌入式Linux学习笔记】platform设备驱动和input子系统

    对于Linux这种庞大的操作系统,代码重用性非常重要,所以需要有相关的机制来提升效率,去除重复无意义的代码,尤其是对于驱动程序,所以就有了platform和INPUT子系统这两种工作机制。 学习视频地址:【正点原子】STM32MP157开发板 platform 驱动框架分为总线、设备和驱动。总

    2024年02月07日
    浏览(44)
  • Linux驱动开发(I2C系统的重要结构体)

    本篇文章来讲解I2C系统的重要结构体,了解这些结构体对于编写I2C驱动来说是至关重要的,所以要想编写好一个I2C驱动程序那么就必须先了解这些结构体。 这里使用百问网的一张图片来讲解: 一个芯片中可以有多个I2C控制器,并且一个I2C控制器可以控制多个设备。那么在对

    2024年02月07日
    浏览(33)
  • 深入探讨Linux驱动开发:Linux设备树

    设备树(Device Tree,简称 DT)是一种在嵌入式系统中描述硬件设备的一种数据结构和编程语言。它用于将硬件设备的配置信息以树形结构的方式进行描述,以便操作系统(如 Linux)可以根据这些信息正确地识别、配置和管理硬件设备。 设备树最初被引入到 Linux 内核中,用于解

    2023年04月27日
    浏览(36)
  • 4、Linux驱动开发:设备-设备号&设备号注册

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(45)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(55)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(34)
  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(37)
  • Linux设备驱动开发 - 虚拟时钟Clock驱动示例

    By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和

    2023年04月21日
    浏览(30)
  • Linux 驱动学习笔记 ——(1)字符设备驱动

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南》学习笔记 字符设备是 Linux 驱动中最基本的一类设备驱动,字节设备就是按照字节流来读写的设备,常见的字符设备包括:LED、蜂鸣器、按键、I2C 以及 SPI 等。 Linux 中一切皆文件,字符设备驱动加载成功后会在 /dev 目录下生成相

    2024年02月08日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包