设备有设备名字、设备号(包括主、次设备号)、设备驱动函数三个属性。在/dev目录中包含了所有Linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和windows,dos操作系统不一样。它实际上是一个访问这些外部设备的端口。我们可以非常方便地去访问这些外部设备,和访问一个文件,一个目录没有任何区别。
1、什么是主设备号和次设备号?
主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。
通过执行ls -l 命令,可以看到在修改日期之前,有两个用逗号分隔的数字,分别为主设备号和次设备号。
2、从上层应用到底层驱动的执行过程
以“open("/dev/pin4", O_RDWR)”函数的执行过程为例来说明。
(1)应用程序使用open函数打开代表pin4的设备文件。
(2)open函数触发异常进入内核态中,并调用系统调用中的sys_cal函数。
(3)sys_call函数调用sys_open函数,并根据文件名找到相关的设备号。
(4)根据设备号从驱动链表里找出相应驱动,并执行驱动程序。
(5)返回一个文件句柄给应用程序。
1、驱动代码编写相关API
static int __init xxxx_init(void) //加载模块时的初始化函数,也就是驱动模块的入口函数
static void __exit xxxx_exit(void) //卸载模块时的函数,也就是卸载某个驱动时要执行的函数
module_init(xxxx_init);//表明驱动模块的入口函数
module_exit(xxxx_exit);//表明驱动模块的出口函数
MODULE_LICENSE(“GPL v2”);
Linux是开源的系统,那就要我们遵守一定的规范,我们一般用GPL v2规范,所以 在驱动编写时都要声明一下
获取设备号
MKDEV(MAJOR, MINOR)
MAJOR :主设备号
MINOR :次设备号
向内核注册了一个字符设备,并把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址
实际上就是将file_operations结构体指针fops绑定为设备驱动函数地址,并插入到内核中的驱动链表中
int __register_chrdev(unsigned int major,const char *name,const struct file_operations *fops)
参数
major:主设备号
name:设备名称
fops:文件系统的接口指针
返回:成功返回主设备号,失败返回小于0的整数
注销字符设备,与__register_chrdev配对使用
int unregister_chrdev(unsigned int major, const char *module_name)
参数
major:主设备号
module_name:设备名称
创建class并注册到内核中
struct class *class_create(THIS_MODULE,char *module_name);
参数
module_name:设备名称
返回:class结构体指针
注销class,与class_create配对使用
class_destroy(struct class * cls);
创建一个设备节点
struct device *device_create(struct class *class,NULL,dev_t devt,NULL, char module_name);
参数
class:所要创建的设备所从属的类
devt:设备号
module_name:设备名称
返回:成功返回 0,失败小于0的整数。
注销设备节点,与device_create配对使用
device_destroy(struct classcls,dev_t devt)
1.1 驱动入口函数步骤
(1)注册字符设备,给以主设备号和设备名对应的设备驱动绑定驱动函数,并插入到内核中的驱动链表中
(2)注册类
(3)在类中注册设备,在/dev中自动生成设备文件
1.2驱动出口函数步骤
(1)注销设备
(2)注销类
(3)注销字符设备,并将相应驱动从内核中的驱动链表移出
2、内核驱动编译步骤
(1)把驱动代码拷贝到driver/char
(2)修改Makefile,告诉编译器,要编译该驱动文件
(3)通过 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 编译生成模块 .ko文件
3、驱动测试
(有些命令需要用sudo指令使用管理者权限)
加载驱动模块
sudo insmod xxx.ko
卸载驱动模块
sudo rmmod xxx //不需要写.ko
查看内核中的模块信息
lsmod
查看模块打印信息
dmesg
手动生成设备,设备名为test,主设备号为8,次设备号为1
sudo mknod test 8 1
查看权限
ls -l /dev/pin4
验证模块加载成功步骤:
(1)装载驱动:sudo insmod xxx.ko
(2)驱动装载后生成设备,比如/dev/pin4,通过sudo chmod 666 /dev/pin4
添加访问权限(所有用户可读可写)
(3)运行测试程序编译的可执行文件pin4test调用驱动
(4)内核的printk相当于内核层的pritnf,通过dmesg查看打印信息
1、进入/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char文件夹 cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char 2、在当前文件夹的Makefile文件添加下面的命令 vi Makefile obj-m += pin4driver.o 3、将pin4driver.c放到/home/sh/Desktop/learnPi/linux-rpi-4.14.y/drivers/char字符设备驱动文件夹 cp ~/Desktop/pin4driver.c . 4、进入/home/sh/Desktop/learnPi/linux-rpi-4.14.y文件夹 cd /home/sh/Desktop/learnPi/linux-rpi-4.14.y 5、编译驱动模块 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 6、将编译完成的.ko文件通过scp发送到树莓派 scp drivers/char/pin4driver.ko pi@192.168.31.106:/home/pi/Desktop 7、加载驱动模块 sudo insmod pin4driver.ko 8、查看内核中的模块信息是否有pin4driver lsmod 9、给/dev/pin4添加权限 sudo chmod 666 /dev/pin4 10、编译pin4test.c输出为pin4test可执行文件 gcc pin4test.c -o pin4test 11、运行pin4test ./pin4test 12、查看打印信息 dmesg
//文件pin4driver.c #include//file_operation声明 #include //module_init module_exit声明 #include //_init _exit声明 #include //class device 声明 #include //copy_from_user的头文件 #include //设备号 dev_t类型声明 #include //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";//模块名 static int pin4_open(struct inode *inode, struct file *file) { printk("pin4_openn");//内核的打印函数,和printf函数 return 0; } static ssize_t pin4_write(struct file *file, const char __user * buf, size_t count, loff_t *ppos) { printk("pin4_writen"); return 0; } static struct file_operations pin4_flops = { .owner = THIS_MODULE, .open = pin4_open, .write = pin4_write, }; int __init pin4_drv_init(void)//真实驱动入口 { int ret; devno=MKDEV(major,minor);//创建设备号 register_chrdev(major, module_name, &pin4_flops);//注册字符设备,并告诉内核把驱动加入到驱动链表中 pin4_class=class_create(THIS_MODULE,"myfirstdemo");//创建类 pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//创建设备文件,让代码在dev下自动生成设备 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");//GPL v2规范
//文件pin4test.c #include#include #include #include #include #include #include int main() { int fd; fd=open("/dev/pin4",O_RDWR); if(fd<0) { printf("load fail!n"); perror("why"); } else printf("load success!n"); write(fd,"1", 1); close(fd); return 0; }



