1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 第三阶段:43-47.树莓派基于Linux内核驱动开发

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

时间:2018-11-05 12:12:52

相关推荐

第三阶段: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: 666binary: 1 1 01 1 01 1 0what to permit: r w xr w xr w xwhat to permit - r: read, w: write, x: executepermission to - user: the owner that create the file/foldergroup: the users from group that owner is memberother: all other users

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

④检查是否执行成功:demsg指令查看内核打印信息

用dmesg命令显示内核缓冲区信息,并通过管道筛选与pin4相关信息

dmesg | grep pin4

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。