linux驱动由浅入深系列:驱动程序的基本结构概览之一(第一个驱动程序)
2017-02-16 15:54
696 查看
本系列导航:
linux驱动由浅入深系列:驱动程序的基本结构概览之一(第一个驱动程序)
linux驱动由浅入深系列:驱动程序的基本结构概览之二(详解驱动注册过程)
提到linux驱动程序,首先应该知道它是linux的内核模块。那么想要编写驱动程序,就要首先认识一下linux的内核模块机制。Linux内核模块是使得复杂而庞大的linux内核条理清晰、可裁剪、高兼容性的重要特性。
Linux内核模块的特点:
1, 模块本身不被编译进内核镜像,能够控制内核的大小。
2, 模块可以在需要的时候中被动态加载,一旦加载完成就和内核其它部分完全一样。
下面便是linux内核模块的helloworld程序,结构十分固定。编译完成后生成hello.ko,通过insmod hello.ko进行加载,加载时输出” hello module has been mount!”,使用rmmod hello进行卸载,卸载时输出” hellomodule has been remove!”。
在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。
静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间。
动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。
在台式机上,一般采用动态加载的方式;在嵌入式产品里,可以先采用动态加载的方式进行调试,调试成功后再编译进内核。
下面是一个最基础的helloworld版的linux驱动,加载入内核后生成/dev/hello节点,打开该文件输出” hello open”。这个驱动并不具有任何控制硬件的行为,只是为了展示linux驱动的通用结构。这几乎是所有驱动程序的通用模版,如led的驱动程序,只需要在hello_ioctl函数中根据不同的传入参数操作gpio寄存器即可。(应用层没有操作硬件的权限,而内核中具有所有权限。驱动程序的作用就是高效的、封装的、有限的向应用层提供服务)
程序分析:
1, 可以看出这个驱动模版也是基于我们上一个内核模块模版的。
2, 驱动模块一般在开机时逐次加载,加载成功后就会调用hello_init函数使用platform_driver_register向内核中注册一个驱动。搞定,就是如此简单!
3, 相关函数
字符设备申请设备号函数register_chrdev、register_chrdev_region
register_chrdev注册指定主次设备号的设备,手动在/dev下创建该设备的设备节点
register_chrdev_region 注册一个指定主次设备号的设备,后利用class类在/dev/目录下自动创建一个该设备的节点。
字符设备注册函数cdev_add。
本实例为简洁起见使用了misc_register函数,该函数已包含了上面两个步骤。
字符设备驱动注册函数platform_driver_register。
、
4, probe函数的调用
当设备和驱动的名字匹配,BUS就会调用驱动的probe函数。这分两种情况:
a 先注册设备,后注册驱动
此种方式最为常见,大多数设备先于驱动注册到内核中。
在内核源代码中,platform 设备的初始化(注册)用arch_initcall()调用,它的initcall 的level为3;而驱动的注册用module_init()调用,即device_initcall(),它的initcall 的level为6。kernel 初始化时(kernel_init@init/main.c),按照内核链接文件中(arm系统:kernel/arch/arm/vmlinux.lds)的__initcall_start段的序列依次执行,这样level小的初始化函数先于level大的初始化函数被调用。
所以platform设备先被注册,驱动加载时会调用驱动程序中的probe(),扫描系统中已注册的设备,找到匹配设备后将驱动和设备绑定。
当设备注册的时候,由于驱动尚未注册,所以执行"__device_attach"时直接返回,未执行"driver_probe_device"进行设备和驱动匹配;后来驱动注册的时候,执行"__driver_attach"的时候设备已经注册,所以进入"driver_probe_device",接着进入"driver_probe_device",然后"really_probe"完成设备和驱动的绑定。
b 先注册驱动,再注册设备
当驱动注册的时候,由于设备尚未注册,所以执行"__driver_attach"时直接返回,未执行"driver_probe_device"进行驱动和设备匹配;后来设备注册的时候,执行"__device_attach"的时候驱动已经注册,所以进入"driver_probe_device",接着进入"driver_probe_device",然后"really_probe"完成驱动和设备的绑定。
本示例使用了最为简洁明了的展示方法,在内核模块的加载函数hello_init中依次注册了设备与驱动。可知注册后者时将会触发hello_probe函数的调用,其中调用misc_register使用了主设备号10,动态分配了次设备号等,完成设备注册。
5, 验证
编译驱动进内核,重新启动在开机过程中看到hello模块成功挂载,probe函数调用。
在/dev目录下生成了hello设备节点,主设备号10,次设备号56。
编写应用层程序进行测试:
编译后运行结果如下:
Shell 打印
Kernel log
linux驱动由浅入深系列:驱动程序的基本结构概览之一(第一个驱动程序)
linux驱动由浅入深系列:驱动程序的基本结构概览之二(详解驱动注册过程)
提到linux驱动程序,首先应该知道它是linux的内核模块。那么想要编写驱动程序,就要首先认识一下linux的内核模块机制。Linux内核模块是使得复杂而庞大的linux内核条理清晰、可裁剪、高兼容性的重要特性。
Linux内核模块的特点:
1, 模块本身不被编译进内核镜像,能够控制内核的大小。
2, 模块可以在需要的时候中被动态加载,一旦加载完成就和内核其它部分完全一样。
下面便是linux内核模块的helloworld程序,结构十分固定。编译完成后生成hello.ko,通过insmod hello.ko进行加载,加载时输出” hello module has been mount!”,使用rmmod hello进行卸载,卸载时输出” hellomodule has been remove!”。
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Radia"); static int hello_init() { printk(KERN_EMERG "hello module has been mount!\n"); return 0; } static void hello_exit() { printk(KERN_EMERG "hello module has been remove!\n"); } module_init(hello_init); module_exit(hello_exit);
在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载。
静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低。若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间。
动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。
在台式机上,一般采用动态加载的方式;在嵌入式产品里,可以先采用动态加载的方式进行调试,调试成功后再编译进内核。
下面是一个最基础的helloworld版的linux驱动,加载入内核后生成/dev/hello节点,打开该文件输出” hello open”。这个驱动并不具有任何控制硬件的行为,只是为了展示linux驱动的通用结构。这几乎是所有驱动程序的通用模版,如led的驱动程序,只需要在hello_ioctl函数中根据不同的传入参数操作gpio寄存器即可。(应用层没有操作硬件的权限,而内核中具有所有权限。驱动程序的作用就是高效的、封装的、有限的向应用层提供服务)
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/fs.h> #define DRIVER_NAME "hello" #define DEVICE_NAME "hello" MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Radia"); static int hello_open(struct inode *inode, struct file *file){ printk(KERN_EMERG "hello open\n"); return 0; } static int hello_release(struct inode *inode, struct file *file){ printk(KERN_EMERG "hello release\n"); return 0; } static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ printk("cmd is %d, arg is %d\n", cmd, arg); return 0; } static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .unlocked_ioctl = hello_ioctl, }; static struct miscdevice hello_dev = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &hello_fops, }; static int hello_probe(struct platform_device *pdv) { printk(KERN_EMERG "hello probe\n"); misc_register(&hello_dev); return 0; } static int hello_remove(struct platform_device *pdv) { printk(KERN_EMERG "hello remove\n"); misc_deregister(&hello_dev); return 0; } static void hello_shutdown(struct platform_device *pdv) { } static int hello_suspend(struct platform_device *pdv, pm_message_t pmt) { return 0; } static int hello_resume(struct platform_device *pdv) { return 0; } static struct platform_driver hello_driver = { .probe = hello_probe, .remove = hello_remove, .shutdown = hello_shutdown, .suspend = hello_suspend, .resume = hello_resume, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, } }; static int hello_init(void) { int driver_state; printk(KERN_EMERG "hello module has been mount!\n"); driver_state = platform_driver_register(&hello_driver); printk(KERN_EMERG "platform_driver_register driver_state is %d\n", driver_state); platform_device_register_simple(DRIVER_NAME, -1, NULL, 0); printk(KERN_EMERG "platform_device_register_simple end\n"); return 0; } static void hello_exit(void) { printk(KERN_EMERG "hello module has been remove!\n"); platform_driver_unregister(&hello_driver); } module_init(hello_init); module_exit(hello_exit);
程序分析:
1, 可以看出这个驱动模版也是基于我们上一个内核模块模版的。
2, 驱动模块一般在开机时逐次加载,加载成功后就会调用hello_init函数使用platform_driver_register向内核中注册一个驱动。搞定,就是如此简单!
3, 相关函数
字符设备申请设备号函数register_chrdev、register_chrdev_region
register_chrdev注册指定主次设备号的设备,手动在/dev下创建该设备的设备节点
register_chrdev_region 注册一个指定主次设备号的设备,后利用class类在/dev/目录下自动创建一个该设备的节点。
字符设备注册函数cdev_add。
本实例为简洁起见使用了misc_register函数,该函数已包含了上面两个步骤。
字符设备驱动注册函数platform_driver_register。
、
4, probe函数的调用
当设备和驱动的名字匹配,BUS就会调用驱动的probe函数。这分两种情况:
a 先注册设备,后注册驱动
此种方式最为常见,大多数设备先于驱动注册到内核中。
在内核源代码中,platform 设备的初始化(注册)用arch_initcall()调用,它的initcall 的level为3;而驱动的注册用module_init()调用,即device_initcall(),它的initcall 的level为6。kernel 初始化时(kernel_init@init/main.c),按照内核链接文件中(arm系统:kernel/arch/arm/vmlinux.lds)的__initcall_start段的序列依次执行,这样level小的初始化函数先于level大的初始化函数被调用。
所以platform设备先被注册,驱动加载时会调用驱动程序中的probe(),扫描系统中已注册的设备,找到匹配设备后将驱动和设备绑定。
当设备注册的时候,由于驱动尚未注册,所以执行"__device_attach"时直接返回,未执行"driver_probe_device"进行设备和驱动匹配;后来驱动注册的时候,执行"__driver_attach"的时候设备已经注册,所以进入"driver_probe_device",接着进入"driver_probe_device",然后"really_probe"完成设备和驱动的绑定。
b 先注册驱动,再注册设备
当驱动注册的时候,由于设备尚未注册,所以执行"__driver_attach"时直接返回,未执行"driver_probe_device"进行驱动和设备匹配;后来设备注册的时候,执行"__device_attach"的时候驱动已经注册,所以进入"driver_probe_device",接着进入"driver_probe_device",然后"really_probe"完成驱动和设备的绑定。
本示例使用了最为简洁明了的展示方法,在内核模块的加载函数hello_init中依次注册了设备与驱动。可知注册后者时将会触发hello_probe函数的调用,其中调用misc_register使用了主设备号10,动态分配了次设备号等,完成设备注册。
5, 验证
编译驱动进内核,重新启动在开机过程中看到hello模块成功挂载,probe函数调用。
在/dev目录下生成了hello设备节点,主设备号10,次设备号56。
编写应用层程序进行测试:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> int main(int argc, char *argv[]) { int fd; printf("enter driver test %s %s \r\n", argv[1], argv[2]); char *hello = "/dev/hello"; if((fd = open(hello, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) { printf("open %s failed\n", hello); } else { printf("%s fd is %d \r\n", hello, fd); ioctl(fd, atoi(argv[1]), atoi(argv[2])); } close(fd); return 1; }
编译后运行结果如下:
Shell 打印
Kernel log
相关文章推荐
- linux驱动由浅入深系列:驱动程序的基本结构概览之二(详解驱动注册过程)
- linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)
- linux驱动由浅入深系列:usb子系统之一(域、包、事务、传输的基本概念)
- UBuntu8.10 开发第一个基本Linux 驱动内核模块
- 菜鸟WDF驱动开发系列(3):安装与调试第一个UMDF驱动程序 推荐
- Linux驱动开发学习 第一个驱动程序 hello world
- JDBC系列-<驱动加载原理全面解析>-<JDBC层次结构和基本构成>-存储过程 CallableStatement(创建和使用)
- Linux驱动程序开发001 - 驱动程序基本框架
- linux驱动由浅入深系列:输入子系统之一(input子系统概述、应用层读取event)
- linux驱动由浅入深系列:tinyalsa(tinymix/tinycap/tinyplay/tinypcminfo)音频子系统之一
- 菜鸟WDF驱动开发系列(2):调试第一个KMDF驱动程序
- Linux系列-文件系统基本结构和文件基本操作管理
- linux驱动由浅入深系列:输入子系统之二(编写一个gpio_key驱动)
- linux驱动学习(2)-第一个驱动程序hello world
- windows过滤驱动程序设计入门(驱动程序基本结构,设备栈,IRP栈和工作原理)
- 菜鸟WDF驱动开发系列(3):安装与调试第一个UMDF驱动程序
- 【Linux驱动】第一个驱动程序
- linux驱动由浅入深系列:中断申请及下半部处理机制
- Linux驱动开发系列之二:第一个linux驱动hello word程序
- Linux字符驱动程序的基本结构与函数