第三阶段:43-47.树莓派基于Linux内核驱动开发

目录
一、驱动认知
1.1 为什么要学习写驱动
1.2 文件名与设备号
1.3 open函数打通上层到底层硬件的详细过程
二、基于框架编写驱动代码
2.1 编写上层应用代码
2.2 修改内核驱动框架代码
2.3 部分代码解读
2.3.1 static的作用
2.3.2 结构体成员变量赋值方式
2.3.3 结构体file_operations(最终加载到内核驱动链表)
2.3.4 手动生成设备
三、驱动代码编译和测试
3.1 驱动框架的模块编译并发送至树莓派
①Makefile内添加生成.o命令
②模块编译生成.ko文件
③把.ko文件发送至树莓派
3.2 上层代码交叉编译发送至树莓派
3.3 树莓派装载驱动并运行
①树莓派加载内核驱动(insmod)
②运行上层代码(无权限)
③增加访问权限再运行
④检查是否执行成功:demsg指令查看内核打印信息

一、驱动认知
1.1 为什么要学习写驱动

树莓派开发简单是因为有厂家提供的wiringPi库,实现超声波,实现继电器操作,做灯的点亮…都非常简单。
但未来做开发时,不一定都是用树莓派,则没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。
学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。
用树莓派学习的目的不仅是为是体验其强大便捷的wiringPi库,更要通过树莓派学会linux内核开发,驱动编写等,做一个属于自己的库。
1.2 文件名与设备号
linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?
依靠文件名与设备号。在/dev下ls -l可以看到
在这里插入图片描述
在这里插入图片描述
设备号又分为:主设备号用于区别不同种类的设备;次设备号区别同种类型的多个设备。
内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:

编写完驱动程序
加载到内核 用户空间open后,调用驱动程序(驱动程序就是操作寄存器来驱动IO口,单片机51,32就是这种操作)

驱动插入到链表的位置(顺序)由设备号检索。
1.3 open函数打通上层到底层硬件的详细过程
用户空间调用open(比如open("/dev/pin4",O_RDWR))产生一个软中断(中断号是0x80),进入内核空间调用sys_call,这个sys_call在内核里面是汇编的,用Source Insight搜索不到。

sys_calll真正调用的是sys_open(属于VFS层虚拟文件系统,因为磁盘的分区和引脚分区不一样,为了实现上层统一化),根据你的设备名比如pin4去到内核的驱动链表,根据其主设备号与次设备号找到相关驱动函数。

调用驱动函数里面的open,这个open就是对寄存器的操作,从而设置IO口引脚电平。这件事对于单片机来说特变容易,就两句话搞定:

sbit pin4 = P1^4;
pin4=1;

(对应下图的粉色笔迹)
在这里插入图片描述
二、基于框架编写驱动代码
2.1 编写上层应用代码

目的是用简单的例子展示从用户空间到内核空间的整套流程。

根据上面提到的驱动认知,有个大致的概念,以open为例子:
上层open→sys_call→sys_open→内核驱动链表节点→执行节点里的open
当然,没有装载驱动的话这个程序执行一定会报错。只有在内核装载了驱动并且在/dev下生成了“pin4”这样一个设备才能运行。

接下来介绍最简单的字符设备驱动框架。

2.2 修改内核驱动框架代码
所谓框架,就是在往驱动链表里面加驱动的时候要符合内核规则,它是定死的东西,基本的语句必须要有,少一个都不行。

虽然有这么多的代码,但核心运行的就两个printk。

zd@ubuntu:~/SYSTEM/linux-rpi-4.14.y/drivers/char$ vi pin4driver2.c
#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";   //模块名 上层的名字

//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数,和printf类似
   
    return 0;
}

//pin4_write函数  因为上层需要open和write这两个函数 
//            如果上层需要调用read等其他函数,可用SourceInsight去内核源码搜索,照着格式修改即可使用 在file_operations结构体里面
static ssize_t pin4_write(struct file *file1,const char __user *buf,size_t count, loff_t *ppos)
{
	printk("pin4_write\n");
    return 0;
}

static struct file_operations pin4_fops = {//内核定义好的结构体 内核源码里有
                                           //就是驱动的结构体 要加载到内核驱动链表
    .owner = THIS_MODULE, 
    .open  = pin4_open,  //上层有读 底层就要有open的支持
    .write = pin4_write, //上层有写 底层就要有write的支持
};




int __init pin4_drv_init(void)   //驱动的真正入口
{

    int ret;
    devno = MKDEV(major,minor);//创建设备号 
    
   //********************注册驱动 加载到内核驱动链表***********
                       //主设备号231 模块名pin4 上面的结构体
    ret   = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
    pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //由代码在/dev下自动生成设备  也可以手动生成设备
    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);       //删除设备  /dev底下的 上面也是创建了设备和类
    class_destroy(pin4_class);              //删除类
    unregister_chrdev(major, module_name);  //卸载驱动 就是删除链表节点的驱动

}

module_init(pin4_drv_init);  //入口:内核加载驱动的时候,这个宏(module_init它不是个函数)会被调用,而真正的驱动入口是它里面调用的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

2.3 部分代码解读
2.3.1 static的作用

内核代码数量庞大,为了防止与其他的文件有变量命名冲突,static限定变量的作用域仅仅只在这个文件。内核源码里面运用了大量的static,因为内核源码众多,一万五千多个C文件,很容易造成代码命名的冲突。

2.3.2 结构体成员变量赋值方式

static struct file_operations pin4_fops = {

    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};

这是内核代码中常见的对结构体的操作方式,单独给指定结构体某些元素赋值。

注意:在keil的编译工具环境中不允许这样写,linux可以。

2.3.3 结构体file_operations(最终加载到内核驱动链表)
在SourceInsight中查看结构体file_operations,可以发现很多的函数指针(指向函数的指针,函数内进行一些程序的执行),这些函数名跟系统上层对文件的操作差不多。(read,write,llseek)(在课程视频9:36)
在这里插入图片描述
如果上层想要实现read,就复制过来,按照格式改一改就能使用。
上层对应底层,上层想要用read,底层就要有read的支持。

2.3.4 手动生成设备
框架中有自动生成设备的代码,那么手动生成设备是怎么样的呢?(一般不这样干,麻烦,仅作为演示)

进入/dev目录,查看帮助可知道创建规则
sudo mknod 设备名称 设备类型 主设备号 次设备号
在这里插入图片描述
使用如下命令创建名称为zhu,主设备号为8,次设备号为1的字符设备。

sudo mknod zhu c 8 1

用 ls -l可以看到已经创建成功
在这里插入图片描述
三、驱动代码编译和测试
3.1 驱动框架的模块编译并发送至树莓派

在ubuntu中,进入Linux内核源码(前一章节编译好的)字符设备驱动目录linux-rpi-4.14.y/drivers/char(IO口属于字符设备驱动)。进入源码目录下的原因是,写驱动必须要链接到源码(源码定义好了结构体等等),必须要有源码。
拷贝上文分析过的驱动框架代码,拿到这个文件夹下 ,并创建成名字为pin4drive.c的文件

在这里插入图片描述
①Makefile内添加生成.o命令
进行配置,使得工程编译时可以编译这个文件
当然不一定要放在/char下。但注意:放在哪个文件夹下,就修改那个文件夹的Makefile即可。
在这里插入图片描述

在这里插入图片描述
模仿这些文件的编译方式,以编译成模块的形式(还有一个方式为编译进内核)编译pin4drive.c
在Makefile里面添加:

obj-m                           += pin4drive2.o

m就是模块的形式
②模块编译生成.ko文件
之前编译内核镜像的时候用的是这个命令:
在这里插入图片描述
现在只需进行模块编译,不需要生成zImage,dtbs文件;
回到源码目录/linux-rpi-4.14.y再执行下面指令
在这里插入图片描述
注:如果说编译中途提示出错,照着错误提示去修改.c文件即可,和上层编译类似。
编译完成生成的一些文件如下:
在这里插入图片描述
③把.ko文件发送至树莓派

scp pin4drive.ko pi@192.168.101.19:/home/pi(别人视频的地址)

之前犯的一个小错误是树莓派和ubuntu的ip地址一样,导致连接不上,修改树莓派的ip地址即可

3.2 上层代码交叉编译发送至树莓派
拷贝上文分析的上层代码到ubuntu中,此处我命名为pin4test.c

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

int main()
{
	int fd;
	fd = open("/dev/pin4",O_RDWR);
	if(fd < 0){
		printf("open failed\n");
		perror("reson");
	}else{
		printf("open success\n");
	}
	fd = write(fd,'1',1);//写一个字符'1',写一个字节
	return 0;
}

使用交叉编译工具在Linux进行编译

arm-linux-gnueabihf-gcc pin4drivertest.c -o pin4test

发送至树莓派

scp pin4test pi@192.168.101.19:/home/pi

3.3 树莓派装载驱动并运行
①树莓派加载内核驱动(insmod)

sudo insmod pin4drive2.ko

查看是否已经成功添加驱动
在这里插入图片描述

可以去设备下查看

ls /dev/pin4 -l

看到驱动添加成功,主设备号231,次设备号0,和内核里面的代码对应上。
在这里插入图片描述
或者lsmod查看内核挂载的驱动
在这里插入图片描述

如果需要卸载驱动,就sudo rmmod pin4drive
在这里插入图片描述

②运行上层代码(无权限)

./pin4test

发现没有对设备pin4的访问权限
在这里插入图片描述
crw是超级用户所拥有的权限,而框中两类用户则无读写的权限(下面有详细说明)
在这里插入图片描述
③增加访问权限再运行
解决方法1:加超级用户

sudo ./pin4test

解决方法2:增加“所有用户都可以访问的权限”(建议)

sudo chmod 666 /dev/pin4

运行成功:
在这里插入图片描述
拓展 >> chmod 命令用于更改文件/文件夹的属性(读,写,执行)

permission to:  user(u)   group(g)   other(o)     
                /¯¯¯\      /¯¯¯\      /¯¯¯\
octal:            6          6          6
binary:         1 1 0      1 1 0      1 1 0
what to permit: r w x      r w x      r w x

what to permit - r: read, w: write, x: execute

permission to  - user: the owner that create the file/folder
                 group: the users from group that owner is member
                 other: all other users

EG: chmod 744 仅允许用户(所有者)执行所有操作,而组和其他用户只允许读。

④检查是否执行成功:demsg指令查看内核打印信息
用dmesg命令显示内核缓冲区信息,并通过管道筛选与pin4相关信息

dmesg | grep pin4

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值