对于内核驱动代码编写而言,它不像我们写FTP项目那样完全是一步步编写完成,驱动代码编写是基于内核特定框架编写得,里面涉及到底层相关得API也是不要我们去记忆得,我们只需要去注意基于框架编写驱动、加载驱动、测试驱动、查看内核打印信息如何实现即可。
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"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_read函数
static int pin4_read(struct file *file, char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_read\n");
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,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,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void)
{
int ret;
devno = MKDEV(major,minor); //创建设备号
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);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //,注意这个不是函数调用,它是一个宏,是加载内核的入口,内核加载驱动的时候,这个宏会被调用,即pin4_drv_init()这个函数会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
~
测试代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
int n_write;
int n_read;
char buf[12] = {0};
fd = open("/dev/pin4", O_RDWR);
if (fd == -1){
printf("open /dev/pin4 failuer\n");
perror("failuer reason");
}
else {
printf("fd = %d\n", fd);
printf("open /dev/pin4 success\n");
}
n_write = write(fd, '8', 1);
printf("write %d bytes\n", n_write);
n_read = read(fd, buf, 12);
printf("read %d bytes\n", n_read);
return 0;
}
2. 如何生成".ko"文件并加载该驱动:
(1)在/home/zhangkun/SYSTEM/linux-rpi-4.14.y/drivers/char
下打开Makefile,添加我们所要编的驱动,即;
其中"m"代表以模块的方式生成该驱动,同时将测试代码交叉编译,即
arm-linux-gnueabihf-gcc pin4Test.c -o pin4Test
(2)将该驱动代码pin4Driver2.c拷贝至/home/zhangkun/SYSTEM/linux-rpi-4.14.y/drivers/char
,并进行编译,即ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
(3)将pin4Driver2.ko、pin4Test拷贝到树莓派中,即scp drivers/char/pin4Driver2.ko pi@172.20.10.6:/home/pi
、scp pin4Test pi@172.20.10.6:/home/pi
:
(4)将内核驱动加载到树莓派中,即sudo insmod pin4Driver2.ko
:
然后,运行pin4Test:
出现这种现象的原因是,我们打开/dev/pin4时没有权限,至=只有root模式下才能open,所以我们需要改变/dev/pin4的权限,即
sudo chmod 666 /dev/pin4
,再次运行pin4Test:
注意到这里read、write都返回的是0而不是1,明明都已经往里面写了字符’8’,为什么还是读写了0个字节呢?原因很简单,就是对于该驱动而言,上层read、write调用的是底层pin4_read、pin4_write,它们的返回值都是0,从而导致我们在终端打印时看到的读写都是0个字节,如果要改成读写都是1个字节,就需要我们将pin4_read、pin4_write的返回值改为1。从这里也可以看出内核驱动的深不可测,要实现read、write底层是需要从很多工作的。
最后通过dmesg
查看内核在运行完pin4Test后打印的信息:
说明上层确实是成功调用了底层的pin4_read、pin4write。
最后,再总结下该驱动整个实现过程:对于内核而言,它本身就是一个代码库,一个程序,一个超级裸机。上层open的时候,通过设备名字即/dev/pin4去调用驱动的,文件名背后则包含主设备号和次设备号,同时产生一个软中断,open去触发系统调用即sys_call,sys_call会穿透虚拟文件系统,虚拟文件系统sys_open会根据/dev/pin4所带设备名、设备号,在内核的驱动链表里面找到对应的驱动, 去调用加载的驱动函数即pin4_open函数。对于read、write也是同样的实现过程。