嵌入式硬件学习(九)——字符设备驱动

一、Linux驱动程序介绍

   linux系统驱动程序分为三大类,字符设备驱动,块设备驱动和网络设备驱动。 一个设备可以属于多种设备驱动类型,比如USB WIFI,由于其使用USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。

  1. 字符设备驱动: 是使用最多的一种,从点灯到IIC, SPI,音频设备等的驱动都是字符设备驱动。
  2. 块设备驱动: 所谓的块设备驱动就是存储器设备的驱动,比如 EMMC、NAND、 SD 卡和U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。
  3. 网络设备驱动: 就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。

二、字符设备驱动

1、字符设备驱动介绍

  字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI、 LCD等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
在这里插入图片描述
  linux系统中万物皆文件,驱动程序加载后会在/dev目录下生成一个对应的文件,如/dev/led。应用程序就是先用open打开该文件,用write控制led的亮灭,用read读取led的亮灭,用完之后用close关闭该文件。这里需要注意的是,应用程序运行在用户空间,驱动程序运行在内核空间。应用程序必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。一个open函数执行的过程如下,明显地,驱动程序必须也得有个open函数才行,那么怎么写这个open函数?写在哪里?如何编译就是我们接下来要学习地内容。
在这里插入图片描述

2、编写驱动程序源码

  linux源码中字符设备驱动程序存放在driver/char目录下,我们也可以将我们自己的驱动程序保存在该目录下,如下图所示。有了这个.c文件之后如何去编译呢?在此之前先介绍一个知识点——编译流程和Makefile编写。
请添加图片描述

3、编译流程和Makefile编写

(1)首先介绍一个编译和链接的问题,编写三个函数放在一个文件夹下面,分别为main.c、add.c、sub.c。
在这里插入图片描述
(2)用命令gcc -oapp main.c add.c sub.c 进行编译就会的到一个app的可执行文件,./app就输出结果。
在这里插入图片描述
(3)上述生成app的过程可以拆分为编译链接两个过程。首先只编译不链接:gcc -c main.c -omain.o , 在这个过程中,会编译出目标文件,但是有个问题是:无法知道在main函数中的add和sub两个函数在哪找,就到了第二步——链接,这一步的目的就是把编译出来的.o文件中的空缺的add和sub跳转地址填补进去。
在这里插入图片描述
(4)如果该文件夹下面的程序多,那么如果只修改或者添加一个程序,使用上面的命令则会将修改的没修改的再编译一遍,导致效率降低。所以一般大型工程项目就会编写一个Makefile文件,该文件的目的就是编译只修改过的程序,搭建一个依赖和目标的关系, 提高开发效率。
在这里插入图片描述
(5)使用命令make进行编译,得到如下结果
在这里插入图片描述

4、编译驱动程序

  此处有两种方法:修改Makefile编写一个Makefile

(1)修改Makefile

  1. 使用vi drivers/char/Kconfig 进入配置文件中
  2. 找到config MINI2440_HELLO_MODULE进行参考并改写一个FIRST_DRIVER改写,如下图所示。
    在这里插入图片描述
  3. 执行make menuconfig,进入到Device Drivers——Character devices,看到有个First driver,选择模式为M,进入Help,拷贝CONFIG_FIRST_DRIVER之后保存退出。
  4. 使用vi drivers/char/Makefile 进入Makefile,添加一行代码,保存退出。
    在这里插入图片描述
  5. 输入make modules 进行编译,通过ls drivers/char/ 可以看到生成了.ko文件。
    在这里插入图片描述
  6. 使用 cp drivers/char/first_driver.ko ~/nfs/rootfs 将生成的驱动程序拷贝到根文件系统下。
  7. 之后在2440中分别使用insmod、lsmod、rmmod加载、查看和卸载驱动程序。
    e2b532c1843d8bbd740ae236f4a59.png)

(2)编写一个Makefile

在这里插入图片描述

5、完善驱动程序

  在first_driver.c中只是编写了init和exit两个函数,但具体功能没有实现。所有驱动程序对应的外设都应该在/dev下面有一个具体的文件,通过open、read、write和close进行数据传递。

(1)字符设备驱动程序注册函数编写

  目前我们编写的是一个字符设备驱动程序,字符设备驱动程序将来被linux加载的时候需要注册这个驱动程序。其实无论哪种驱动程序,按照linux的做法在加载时都需要注册。字符设备驱动程序注册函数为:
在这里插入图片描述
这个函数总共有三个参数,下面一一说明:

  1. unsigned int major:主设备号,一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的设备。简单来说, linux需要一个数来管理某个驱动程序和使用这个驱动程序的设备。很明显,这个设备号具有唯一性。我们可以使用cat /proc/devices命令即可查看当前系统中所有已经使用了的设备号。在接下来的程序中,我们可以设置一个静态的主设备号,比如200。设置时一定要注意不能使用已经用了的主设备号。
    在这里插入图片描述
  2. *const char name:为驱动程序起一个名字
  3. const struct file_operations fops :是一个指向file_operations的结构体指针,这个结构体中变量如下图所示,包含打开、关闭、读和写*等等,这个结构体里面的成员绝大多数都是函数的指针。
    在这里插入图片描述
    ① open 函数用于打开设备文件
    ② release 函数用于释放(关闭)设备文件,与应用程序中的 close函数对应
    ③ read 函数用于读取设备文件
    ④ write 函数用于向设备文件写入(发送)数据
    ⑤ poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写
    ⑥ owner 拥有该结构体的模块的指针,一般设置为THIS_MODULE

(2)完善后的代码

  在这段代码中,首先定义了驱动的初始化函数first_driver_init和销毁函数first_driver_exit ,在初始化函数中需要对这个驱动进行注册register_chrdev,在销毁时需要注销unregister_chrdev。在注册函数中需要定义一个结构体,这个结构体中大多数为函数指针,指向要调用的函数,我们编写了first_driver_open、first_driver_close、first_driver_read和first_driver_write来初始化该指针。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>

int first_driver_open(struct inode *p_node, struct file *fp)
{
	printk("open\n");
	return 0;
}
int first_driver_close(struct inode *p_node, struct file *fp)
{
	printk("close\n");
	return 0;
}
ssize_t first_driver_read(struct file *fp, char __user *user_buffer, size_t n, loff_t *offset)
{
	printk("read\n");
	return 0;

}
ssize_t first_driver_write(struct file *fp, char __user *user_buffer, size_t n, loff_t *offset)
{
	printk("write\n");
	return 0;
}

struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = first_driver_open,
	.release = first_driver_close,
	.read = first_driver_read,
	.write = first_driver_write
};

static int __init first_driver_init(void)
{
	int ret;
	printk("init\n");
	ret = register_chrdev(200, "first driver", &fops);
	if(ret != 0)
	{	
		return ret;
	}
	return 0;
}

static void __exit first_driver_exit(void)
{
	unregister_chrdev(200, "first driver");
	printk("exit\n");
}

module_init(first_driver_init);
module_exit(first_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZY");

(3)字符设备和文件的关联

  进行make编译,将生成的驱动程序.ko拷贝到2440中,之后insmod first_driver.c进行驱动安装,利用cat proc/devices 也可以看到注册的字符设备存在,但是发现/dev下面没有相关文件。
在这里插入图片描述在这里插入图片描述

  此时,需要手动创建一个字符设备和文件关联在一起,使用命令:mknod /dev/first c 200 0,表示创建一个结点,创建到/dev下面first文件,c表示创建的设备为字符设备,200表示主设备号,0表示子设备号。
在这里插入图片描述

(4)主函数调用

  在nfs/rootfs中创建一个main.c,编写程序实现write调用。

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

int main(void)
{
	int fd = open("/dev/first", O_RDWR);
	if(fd < 0)
	{
		puts("error");
		return -1;
	}
	while(1)
	{
		int i = 10;
		write(fd, &i, 4);
		sleep(1);
	}
	return 0;
}

之后arm-linux-gcc编译之后,在2440上面运行得到结果。
在这里插入图片描述

三、如何在linux中实现函数查看跳转

ctags -R .

(1)ctags 命令简介
   ctags是一个用于生成索引文件(tags 文件)的工具。这个索引文件可以帮助文本编辑器(如 Vim)等工具实现快速的代码导航。例如,在一个大型的 C/C++ 或其他编程语言的代码库中,通过索引文件,编辑器可以快速地定位函数、类、变量等各种代码元素的定义位置。
(2) -R选项含义
   -R是ctags命令的一个选项,它表示递归(recursive)。在命令ctags -R.中,.代表当前目录。所以ctags -R.的意思是从当前目录开始,递归地对所有子目录和文件进行扫描,生成一个包含所有代码元素(如函数、类等)索引信息的tags文件。
(3) 使用
  编写好所有.c或者.h文件后,在当前文件夹输入上述命令。之后在main.c中将光标指在函数名的地方,ctrl+] 即可完成跳转,ctrl+t 即可恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值