linux驱动原理-LED驱动分析
ZedBoard Linux开发 ---- OLED驱动详解
ZedBoard Linux开发---- OLED驱动详解但凡单片机的初学者基本都有这样一个试验,也就是点亮第一个LED,类似于程序员的“helloworld”,我当年也是如此。
本来我也是希望从LED开始学习,不过对于ZedBoard来说,可能还找不到现成的LED驱动程序可以学习(事后发现在Digilent Linux内核中,点亮一个LED所要理解的机制其实更加复杂),反而倒是提供了OLED的内核驱动,因此我们就从点亮OLED开始吧。
对于OLED在用户空间的操作,之前介绍了两种方式,即shell以及编写C应用程序,博客链接如下:/thinki_cao/blog/static/8394487520143193932495/OLED的驱动在Linux内核源码中的位置是linux-digilent/drivers/pmods/pmodoled-gpio.c,而在内核配置中的位置是:-> Device Drivers-> Pmod Support (PMODS [=y])(注意make ARCH=arm menuconfig的时候务必要把ARCH加上)并且默认是编译进内核的,而在Digilent OOB Design中是以模块驱动的形式放在ramdisk文件系统中的,因此如果重新编译内核的话,原先的load_oled,unload_oled都是无法使用的,原理很简单,上面两个命令都是脚本,并且是单纯的insmod以及rmmod驱动。
下面就开始看分析pmodoled-gpio.c文件:首先跳到文件最后几行,找到函数module_platform_driver(gpio_pmodoled_driver); 该函数定义在include/linux/platform_device.h文件中:#define module_platform_driver(__platform_driver) \module_driver(__platform_driver, platform_driver_register, \platform_driver_unregister)而module_driver()函数则定义在include/linux/device.h文件中:#define module_driver(__driver, __register, __unregister, ...) \static int __init __driver##_init(void) \{ \return __register(&(__driver) , ##__VA_ARGS__); \} \module_init(__driver##_init); \static void __exit __driver##_exit(void) \{ \__unregister(&(__driver) , ##__VA_ARGS__); \} \module_exit(__driver##_exit);通俗一点来理解的话,module_platform_driver(gpio_pmodoled_driver);最终展开后就是如下形式:static int __init gpio_pmodoled_driver_init(void){return platform_driver_register(&gpio_pmodoled_driver);}module_init(gpio_pmodoled_driver_init);static void __exit gpio_pmodoled_driver_init(void){return platform_driver_unregister(&gpio_pmodoled_driver);}module_exit(gpio_pmodoled_driver_exit);看到module_init()和module_exit()估计就熟悉很多了,其实一开始我也不习惯这些宏,不过后来习惯了以后发现还是有不少好处的,所以我们编写驱动的时候可以参考内核中的代码风格和习惯,非常具有学习意义。
Linux学习之LED驱动程序简介
这里我们当然也要根据实际来思考我们的LED驱动程序。
在STM32点灯的时候,一般输出低电平点灯,输出高电平灭灯。
在嵌入Linux操作系统的情况下,我们自然也要想到有个写1/0的思想。
类比我们上一篇的hello程序:我们的LED程序自然要写入的数据为0/1来点亮、熄灭LED。
这里我们做的实验室与硬件无关的LED实验:我们的驱动程序在收到应用程序发送过来的0时打印led on、收到1时打印led off。
模仿上一篇的hello程序,我们修改得到的与硬件无关的LED程序(核心部分)如下:LED应用程序:LED驱动程序:加载led驱动模块及运行应用程序:与硬件有关的LED驱动上面那一节分享的是与硬件无关的LED驱动实验,主要是为了理清LED驱动的大体思路。
这里我们再加入与硬件有关的相关操作以构造与硬件有关的LED驱动程序。
我们在进行STM32的裸机编程的时候,对一些外设进行配置其实就是操作一些地址的过程,这些外设地址在芯片手册中可以看到:这是地址映射图,这里图中只是列出的外设的边界地址,每个外设又有很多寄存器,这些寄存器的地址都是对外设基地址进行偏移得到的。
同样的,对于NXP 的IMX6ULL芯片来说,也是有类似这样的地址的:此时我们要编写Linux系统下的led驱动,涉及到硬件操作的地方操作的并不是这些地址(物理地址),而是操作系统给我们提供的地址(虚拟地址)。
操作系统根据物理地址来给我们生成一个虚拟地址,我们的led驱动操控这个地址就是间接的操控物理地址。
至于这两个地址是怎么联系起来的,里面个原理我们暂且不展开。
我们从函数层面来看,内核给我们提供了ioremap 函数,这个函数可以把物理地址映射为虚拟地址。
这个函数在内核文件arch/arm/include/asm/io.h 中:void __iomem *ioremap(resource_size_t res_cookie, size_t size); •res_cookie:要映射给的物理起始地址。
Linux字符设备驱动之Tiny6410 LED驱动编写
Linux字符设备驱动之Tiny6410 LED驱动分析摘要:驱动程序是应用程序和底层硬件之间的桥梁,非常重要。
字符设备是一种可以当做一个字节流来存取的设备,这样的设备只能一个字节一个字节的进行数据传输,这样的驱动常常至少实现open、close、read、和write系统条用,常见的有串口、LED、文本控制台等,字符设备通过文件系统节点来存取,例如/dev/tty1和/dev/lp0.在一个字符设备和一个普通文件之间唯一相关的不同就是,你可以在普通的文件中移来移去,但是大部分字符社诶仅仅是数据通道,只能顺序存取。
重要概念1.用户空间和内核空间一个驱动模块在内核空间运行,而应用程序则是在用户空间运行,这个概念是操作系统的理论基础。
Linux为这两种空间之间的数据传输定义了两个函数,分别为copy_to_user()和copy_from_user(),从字面意思可以知道函数的意义。
比如在编写驱动程序时,很显然驱动程序属于内核空间,会经常使用copy_from_user()函数,从应用程序中获取数据给驱动程序。
2.编译模块使用make xxxx modules 生成xxx.ko模块。
3.加载和卸载模块(驱动)驱动生成的模块需要加载到内核中,加载模块使用insmod指令,如:insmodxxx.ko卸载驱动则用rmmod命令。
4.所需头文件#include <linux/module.h> //包含大量加载模块所需的函数和符号定义#include<linux/init.h> //制定初始化和清理函数许可凭证指令:MODULE_LICENSE(“GPL”);5.初始化和关停初始化指令:static int __initinitialization_function(void){}module_init(initialization_function);初始化函数应声明为静态,因为他们不会再特定文件之外可见,毕竟static的重点应用是“隐藏”。
嵌入式Linux下LED报警灯驱动设计及编程
《嵌入式Linux下LED报警灯驱动设计及编程》实验报告学生:学号:专业班级:指导教师:完成时间:实验5 嵌入式Linux下LED报警灯驱动设计及编程一.实验目的理解驱动本质,掌握嵌入式Linux系统下驱动开发相关知识,包括端口寄存器访问、接口函数编写、和文件系统挂接、注册及相关应用编程等知识点。
二.实验容实验5.1 嵌入式Linux下LED报警灯驱动设计及跑马灯应用编程实验5.2 添加看门狗功能的跑马灯应用编程三.预备知识Linux使用、驱动相关知识等四.实验设备及工具(包括软件调试工具)硬件:ARM 嵌入式开发平台、PC 机Pentium100 以上、串口线。
软件: WinXP或UBUNTU开发环境。
五.实验5.1步骤5.1 前期准备(1)看懂相关硬件电路图【见S3C6410实验箱电路图-底板.pdf】,以LED报警灯为例进行设计打开PDF硬件电路图,明确LED灯用到的多个GPIO及其控制器本实验电路 LED1-------GPM0LED2-------GPM1LED3-------GPM2LED4-------GPM3LED5-------GPM4LED6-------GPM5LED7-------GPQ0LED8-------GPQ1得出结论:8个LED灯使用到的硬件控制器分别为GPM和GPQ两个硬件控制器(2)在芯片手册中找到相应的硬件控制器部分,重心是看懂端口寄存器本实验要求完成LED流水灯设计,所以需要设置控制器中端口寄存器:GPMCON----设置相应位为输出口GPMDAT-----控制相应位输出高电平-----点亮LED灯输出低电平-----熄灭LED灯(3) linux核中相关寄存器读写函数读寄存器函数readl(寄存器虚地址);写寄存器函数writel(值(无符号整型), 寄存器虚地址);具体端口寄存器地址宏定义在/opt/FriendlyARM/linux-2.6.38/arch/arm/mach-s3c64xx/include/mach文件夹下的文件中,如端口M寄存器在gpio-bank-m.h文件中有定义:#define S3C64XX_GPMCON (S3C64XX_GPM_BASE + 0x00) #define S3C64XX_GPMDAT (S3C64XX_GPM_BASE + 0x04)5.2 LED报警灯驱动设计s3c6410_leddrv.c(1)头文件包含和相关宏定义#include <linux/miscdevice.h>#include <linux/delay.h>#include <asm/irq.h>//#include <mach/regs-gpio.h>#include <mach/hardware.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/ioctl.h>#include <linux/cdev.h>#include <linux/string.h>#include <linux/list.h>#include <linux/pci.h>#include <asm/uaccess.h>#include <asm/atomic.h>#include <asm/unistd.h>#include <mach/map.h>#include <mach/regs-clock.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <mach/gpio-bank-e.h>#include <mach/gpio-bank-k.h>#define ON 1#define OFF 0(2)编写驱动接口函数/*功能:配置GPM0~5/GPQ0~1为输出口参数:无返回值:无*/void LedConfig(void){//读出端口M控制寄存器(S3C64XX_GPMCON)值,修改并写回相关端口寄存器//add your codeunsigned int tmp;tmp =readl(S3C64XX_GPMCON);tmp &= ~((0XF<<0X0)|(0XF<<0X4)|(0XF<<0X8)|(0XF<<0XC)|(0XF<<0X10)|(0XF<<0X14));tmp |= (0X1<<0X0)|(0X1<<0X4)|(0X1<<0X8)|(0X1<<0XC)|(0X1<<0X10)|(0X1<<0X14);writel(tmp,S3C64XX_GPMCON);}/*功能:点亮第i个LED灯参数:无符号整型变量iLed,表示第i个LED灯返回值:无*/void iLedOn(unsigned int iLed){//读出端口M数据寄存器(S3C64XX_GPKDAT)值,修改并写回相关端口寄存器//add your code hereunsigned int tmp;tmp =readl(S3C64XX_GPMDAT);tmp &= ~((0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5));writel(tmp,S3C64XX_GPMDAT);}/*功能:熄灭第i个LED灯参数:无符号整型变量iLed,表示第i个LED灯返回值:无*/void iLedOff (unsigned int iLed){//读出端口M数据寄存器(S3C64XX_GPKDAT)值,修改并写回相关端口寄存器//add your code hereunsigned int tmp;tmp =readl(S3C64XX_GPMDAT);tmp &= ~((0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5));tmp |= (0X1<<0X0)|(0X1<<0X1)|(0X1<<0X2)|(0X1<<0X3)|(0X1<<0X4)|(0X1<<0X5);writel(tmp,S3C64XX_GPMDAT);}(2)和文件系统接口对接static int s3c6410_led_open(struct inode *inode, struct file *filp){//把之前的端口K控制寄存器值读出来保存起来//调用LedConfig函数,把GPIO口配置成输出口//add your codeold_gpmcon_val=readl(S3C64XX_GPMCON);LedConfig();renturn 0;}static int s3c6410_led _release(struct inode *inode, struct file *filp) {//恢复之前的端口K控制寄存器初始值//add your codewritel(old_gpmcon_val,S3C64XX_GPMCON);renturn 0;}static long s3c6410_led _ioctl(struct file *filp, unsigned int cmd, unsigned long arg){switch(cmd){case ON://点亮所有LED灯//add your codei LedOn();break;case OFF://熄灭所有LED灯break;}}struct file_operations led_fops={.release=___s3c6410_led_release______,.unlocked_ioctl=___s3c6410_led_ioctl____,};(3)添加模块标记代码static int __init led_dev_init(void){int ret;注册设备printk (DEVICE_NAME"\tinitialized\n");return ret;}static void __exit led_dev_exit(void){//注销设备//add your code}module_init(led_dev_init);module_exit(led_dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("licnjupt.");5.2 编写Makefile并加载到核(1)编写all:make –C pwd) modulesclean:rm -rf *.ko *.o(3) 编译使用命令编译:_____#make_____________________________编译完成后生成驱动文件_____leddrv.ko__________________。
实验三 在嵌入式Linux上开发LED控制电路设备驱动程序
实验内容
• 在嵌入式Linux上设计LED控制电路设备驱动 程序ຫໍສະໝຸດ 嵌入式Linux字符型设备
• 嵌入式Linux基本设备类型
– 字符型设备 – 块设备 – 网络设备 – 其他设备(相关的协议栈由kernel附加层支持)
• 嵌入式Linux字符型设备
– 实现和管理简单 – 无需缓冲,直接读写的设备(例如串口设备) – 可以被看作一个类似文件的数据流
与设备驱动程序关联的内核数据结构
Linux内核模块
• Linux模块由没有链接成完整可执行文件的目标 代码组成 • Linux模块可以动态装载(链接)到运行中的内 核中,也可以从内核中动态卸载 • Linux内核提供了对许多模块类型的支持,其中 包括设备驱动程序 • 因为Linux模块运行在内核空间,所以只能调用 内核输出的函数,而不能调用外部库中的函数 (例如只能使用内核输出的printk 函数,而不 能使用libc中的printf函数)
• 申明模块退出函数
– module_exit(cleanup_function);
• 实现模块退出函数
static void __exit cleanup_function(void) { /* Cleanup code here */ }
Linux内核模块管理
• 加载模块(insmod) • 卸载模块(rmmod) • 查询内核中当前加载的模块列表(lsmod)
alteraquartusiiredhatlinuxgnu跨平台开发工具链在嵌入式linux上开发led控制电路设备驱动程序实验?实验原理嵌入式linux设备驱动程序与内核模块嵌入式linux字符型设备嵌入式linux中与设备驱动程序关联的内核数据嵌入式linux中与设备驱动程序关联的内核数据结构嵌入式linux字符型设备驱动程序框架led控制电路设备驱动程序工作原理嵌入式linux设备驱动程序?设备驱动程序是一种可以使计算机和设备通信的特殊程序相当于硬件的接口操作系统通过设备驱动程序来控制硬件设备的工作备的工作?嵌入式linux中设备驱动程序通常是以内核模块的形式存在的linux内核模块?linux模块由没有链接成完整可执行文件的目标代码组成?linux模块可以动态装载链接到运行中的内核中也可以从内核中动态卸载?linux内核提供了对许多模块类型的支持其中包括设备驱动程序?因为linux模块运行在内核空间所以只能调用内核输出的函数而不能调用外部库中的函数例如只能使用内核输出的printk函数而不能使用libc中的printf函数linux内核模块代码结构?申明模块初始化函数moduleinitinitializationfunction
基于嵌入式Linux的LED驱动开发与应用
基于嵌入式Linux的LED驱动开发与应用摘要:简要介绍了基于嵌入式ARM处理器芯片LPC3250的嵌入式Linux的LED驱动程序的开发原理、流程以及相关主要接口硬件电路的设计。
实际运行结果表明,该设计完全达到预期效果。
关键词:嵌入式Linux;LED;硬件;驱动程序0引言随着IT技术和嵌入式技术的快速发展,嵌入式产品已经广泛应用于工业、能源、环保、通信等各个行业,显示出其强大的生命力。
Linux是当今流行的操作系统之一,具有源代码开放、内核稳定、功能强大和可裁减等优点而成为众多应用的首选。
同样嵌入式Linux也继承了Linux的诸多优点。
对Linux应用程序来说,由于设备驱动程序屏蔽了硬件的细节,其硬件设备将作为一个特殊的文件,因此应用程序可以像操作普通文件一样对硬件设备进行操作。
本设计中驱动的设备是基于NXP公司的LPC3250微处理器开发的LED信号指示灯,利用这些指示灯来显示仪器的运行状态,方便用户了解仪器的工作状况。
1LPC3250简介及接口电路设计本设计中主控芯片采用LPC3250微处理器,具有高集成度、高性能、低功耗等特点。
它采用90nm工艺和ARM926EJS内核,主频最高为208MHz,具有全系列标准外设。
其中包括带专用DMA控制器的24位LCD控制器,可支持STN和TFT面板。
充分满足本设计的需要,外部只需加入很少芯片就可实现系统功能<sup>[1]</sup>。
LPC3250共有296个管脚。
对于4个LED灯来说需要用到4个引脚,这里使用GPIO端口来设计,GPM1~GPM3作为LED灯的控制端口,另外还需要为LED提供电源,这里需要3.3V的直流电源。
接口电路设计如图1所示。
GPM0~GPM3分别与电阻、LED连接,当GPM0~GPM3置为低电平时,相应的LED灯点亮。
2驱动程序设计在嵌入式Linux操作系统下,有三类主要的设备文件类型:字符设备、块设备和网络设备<sup>[2]</sup>。
Tiny-S3C6410_Linux下LED灯驱动移植过程
UT-S3C6410 ARM11 Linux 下的LED驱动一、实验环境操作系统:fedora13交叉编译环境:arm-Linux-gcc 或以上,6410板子内核源码路径在:忘了,需要厂家给的内核源代码硬件平台:S3C6410开发板(其他类型的开发板也可以注意配置GPIO)注:交叉编译环境一定要装好,一般的开发板给的配套资料中都会有,安装过程也都有详细的过程,如果没有,亲,你只有自己解决了。
也可以联系我(****************),泪奔支持你们。
二、实验原理控制LED是最简单的一件事情,就像学C语言时候写的“hello world”程序一样,是一个入门的程序。
首先来了解一下相关的硬件知识:UT-S3C6410LED原理图UT-S3C6410LED外部引脚图从上面的原理图可以得知,LED与CPU引脚的连接方法如下,高电平点亮。
LED1 -GPM0LED2 -GPM1LED3 -GPM2LED4 -GPM3从数据手册可以找到相应的控制方法。
这里我们以LED1为例,介绍一下LED1的操作方法,其他的类似,请大家自行分析。
通过上面可以得知,需要先将GPM0设置为输出方式。
将寄存器GPMCON低四位配置成0001。
然后将GPMDAT寄存器的第0位置1灯亮,置LED0灯亮,开发板上有四个LED所以要对GPMDAT的低四位进行操作,就可以实现对灯的亮灭操作了。
三、实验步骤1、编写驱动程序mini6410_leds.c#include <linux/miscdevice.h>#include <linux/delay.h>#include <asm/irq.h>//#include <mach/regs-gpio.h>#include <mach/hardware.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/ioctl.h>#include <linux/cdev.h>#include <linux/string.h>#include <linux/list.h>#include <linux/pci.h>#include <asm/uaccess.h>#include <asm/atomic.h>#include <asm/unistd.h>#include <mach/map.h>#include <mach/regs-clock.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <mach/gpio-bank-e.h>#include <mach/gpio-bank-k.h>#define DEVICE_NAME "leds"static long sbc2440_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {switch(cmd) {unsigned tmp;case 0:case 1:if (arg > 4) {return -EINVAL;}tmp = readl(S3C64XX_GPKDAT);tmp &= ~(1 << (4 + arg));tmp |= ( (!cmd) << (4 + arg) );writel(tmp, S3C64XX_GPKDAT);//printk (DEVICE_NAME": %d %d\n", arg, cmd); return 0;default:return -EINVAL;}}static struct file_operations dev_fops = {.owner = THIS_MODULE,.unlocked_ioctl = sbc2440_leds_ioctl,};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,};static int __init dev_init(void){int ret;{unsigned tmp;tmp = readl(S3C64XX_GPKCON);tmp = (tmp & ~(0xffffU<<16))|(0x1111U<<16); writel(tmp, S3C64XX_GPKCON);tmp = readl(S3C64XX_GPKDAT);tmp |= (0xF << 4);writel(tmp, S3C64XX_GPKDAT);}ret = misc_register(&misc);printk (DEVICE_NAME"\tinitialized\n");return ret;}static void __exit dev_exit(void){misc_deregister(&misc);}module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("FriendlyARM Inc.");(1)把Hello,Module 加入内核代码树,并编译一般编译2.6 版本的驱动模块需要把驱动代码加入内核代码树,并做相应的配置,如下步骤(注意:实际上以下步骤均已经做好,你只需要打开检查一下直接编译就可以了):Step1:编辑配置文件Kconfig,加入驱动选项,使之在make menuconfig 的时候出现打开linux-2.6.38/drivers/char/Kconfig 文件,添加如图所示:#====================cgf add===================================== config MINI6410_LEDStristate "LED Support for Mini6410 GPIO LEDs"depends on CPU_S3C6410default yhelpThis option enables support for LEDs connected to GPIO lineson Mini6410 boards.#================================================================== 保存退出,这时在linux-2.6.38 目录位置运行一下make menuconfig 就可以在DeviceDrivers Character devices 菜单中看到刚才所添加的选项了,按下空格键将会选择为<M>,此意为要把该选项编译为模块方式;再按下空格会变为<*>,意为要把该选项编译到内核中,在此我们选择<M>,如图,如果没有出现,请检查你是否已经装载了缺省的内核配置文件,(2)Makefile文件Step2:通过上一步,我们虽然可以在配置内核的时候进行选择,但实际上此时执行编译内核还是不能把mini6410_leds.c编译进去的,还需要在Makefile 中把内核配置选项和真正的源代码联系起来,打开linux-2.6.38-cgf/drivers/char/Makefile,obj-$(CONFIG_MINI6410_LEDS) += mini6410_leds.o添加并保存退出Step3:这时回到linux-2.6.38 源代码根目录位置,执行make modules,就可以生成我们所需要的内核模块文件drivers/char/mini6410_leds.ko 了,注意:执行make modules 之前,必须先执行make zImage,只需一次就可以了。
第四章 LED驱动程序 Linux设备驱动程序 教学课件
目录
嵌入式Linux字符设备的驱动程序结构 设备驱动程序中的问题 GPIO原理介绍 LED的驱动程序实例及测试
I/O端口
驱动程序是介于操作系统和系统硬件之间 的负责通信的接口,它把软件和硬件分开, 其作用如图:
作。如清除缓冲区。如果设备是独占的,则open函 数必须将设备标记成忙状态。
Close入口点
Close函数负责关闭设备的操作。 当最后一次使用设备完成后,调用close函数,关闭
设备文件。独占设备必须标记为可再次使用。
字符设备驱动程序的入口
Read入口点
Read函数负责从设备上读数据和命令,有缓冲区的 I/O设备操作一般是从缓冲区里读数据。
loff_t * f_ops) {
return count; }
// ------------------- WRITE ----------------------ssize_t GPIO_LED_write (struct file * file ,const char * buf, size_t
这些通用的GPIO接口是可配置的。每个端口都可以通 过软件配置寄存器来满足不同系统和设计的需要。在运 行主程序之前,必须先对每一个用到的引脚的功能进行 设置。如果某些引脚的复用功能没有使用,那么可以先 将该引脚设置为I/O端口。
S3C2410X芯片与端口相关的寄存器
端口控制寄存器(GPACON—GPHCON)
Linux的设备文件是同硬件一一对应的,因而对设备的 操作可以通过对设备文件的操作来实现。而这些操作方 式其实就是一些标准的系统调用,如open()、read()、 write()、close()等。实际上,file_operations就是把系 统调用和驱动程序关联起来的关键数据结构。这个结构 的每个成员都对应着一个系统调用。
Linux驱动之LED驱动编写
Linux驱动之LED驱动编写从上到下,⼀个软件系统可以分为:应⽤程序、操作系统(内核)、驱动程序。
结构图如下:我们需要做的就是写出open、read、write等驱动层的函数。
⼀个LED驱动的步骤如下:1、查看原理图,确定需要控制的IO端⼝打开原理图,确定需要控制的IO端⼝为GPF4、GPF5、GPF6。
2、查看芯⽚⼿册,确定IO端⼝的寄存器地址,可以看到它的基地址为0x560000503、编写驱动代码,编写驱动代码的步骤如下:1)、编写出⼝、⼊⼝函数。
a、⾸先利⽤register_chrdev函数如果第⼀个参数为0的话那么会⾃动分配⼀个主设备号为Firstmajor ;第⼆个参数firstled_drv会是这个字符设备的名称可以利⽤命令cat /proc/devices看到;第三个参数是它的first_drv_fops结构体,这个结构体是字符设备中最主要的,后⾯再说明。
b、接着利⽤class_create函数创建⼀个firt_drv_class类。
它的第⼀个参数指向这个模块,第⼆个参数为类的名称。
再利⽤class_device_create创建四个设备节点,第⼀个参数为类、第三个参数为设备号,第五个参数为设备节点的名称,第六个参数为次设备号。
这样的话会在加载驱动之后⾃动在/dev⽬录下创建四个设备⽂件。
c、ioremap函数重映射函数,将物理地址转换成虚拟地址 d、a-c为驱动⼊⼝函数,在驱动出⼝函数会将a-c创建的东西全部删除。
e、module_init与module_exit表⽰在insmod与rmmod的时候内核会调⽤first_ledsdrv_init与first_ledsdrv_exit/** 执⾏insmod命令时就会调⽤这个函数*/static int __init first_ledsdrv_init(void){int minor;//次设备号Firstmajor = register_chrdev(0, "firstled_drv", &first_drv_fops);//注册first_drv_fops结构体到字符设备驱动表,0表⽰⾃动分配主设备号if(Firstmajor<0){printk(" first_drv can't register major number\n");return Firstmajor;}firt_drv_class = class_create(THIS_MODULE, "leds");//创建类firt_drv_class_dev[0] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, 0), NULL, "leds");//创建设备节点if (unlikely(IS_ERR(firt_drv_class_dev[0])))return PTR_ERR(firt_drv_class_dev[0]);for(minor=1;minor<4;minor++){firt_drv_class_dev[minor] = class_device_create(firt_drv_class, NULL, MKDEV(Firstmajor, minor), NULL, "led%d",minor);//创建设备节点if (unlikely(IS_ERR(firt_drv_class_dev[minor])))return PTR_ERR(firt_drv_class_dev[minor]);}gpfcon = ioremap(0x56000050 , 16);//重映射,将物理地址变换为虚拟地址gpfdat = gpfcon + 1;printk("firstdrv module insmoded\n");return0;}/** 执⾏rmmod命令时就会调⽤这个函数*/static void __exit first_ledsdrv_exit(void){int i;for(i=0;i<4;i++)class_device_unregister(firt_drv_class_dev[i]);//删除设备节点class_destroy(firt_drv_class);//删除类iounmap(gpfcon);//删除重映射分配的地址unregister_chrdev(Firstmajor, "firstled_drv");//将rst_drv_fops结构体从字符设备驱动表中删除printk("firstdrv module rmmod\n");}/* 这两⾏指定驱动程序的初始化函数和卸载函数 */module_init(first_ledsdrv_init);module_exit(first_ledsdrv_exit);2)、添加file_operations 结构体,这个是字符设备驱动的核⼼结构,所有的应⽤层调⽤的函数最终都会调⽤这个结构下⾯定义的函数。
嵌入式linux应用开发-基础知识-第六章 led驱动程序框架
在嵌入式Linux应用开发中,特别是硬件驱动层面,LED(发光二极管)驱动程序框架通常用于控制硬件板上的LED灯。
第六章关于LED驱动程序框架的内容可能涵盖了以下几个关键点:1. 分层结构:LED驱动程序常常分为两层或多层设计,例如leddrv.c和board_demo.c。
leddrv.c负责实现与LED设备控制器的交互,定义基本操作如打开、关闭、设置亮度等。
board_demo.c则针对具体目标板进行适配,将通用的LED驱动接口与实际的硬件引脚连接起来,处理与硬件相关的初始化和控制逻辑。
2. 内核驱动注册:在Linux内核中,LED驱动需要注册为一个字符设备或平台设备驱动。
早期的驱动编写可能会通过module_init函数来调用alloc_chrdev_region分配设备号,然后使用cdev_alloc、cdev_init以及cdev_add等函数来创建和添加字符设备。
3. LED类与子系统:自Linux 2.6内核开始引入了更完善的LED子系统,使得LED驱动可以更加简洁地集成到内核中。
现在通常会使用leds_class提供的API来注册LED设备,这样可以统一管理多个LED,并支持动态增减LED设备。
4. 用户空间接口:应用层可以通过文件系统的接口(如路径下)来操作LED,通过open、write、ioctl等系统调用来控制LED的状态和行为。
5. 设备树支持:在现代嵌入式Linux系统中,设备树(Device Tree)被广泛用于描述硬件资源,LED驱动也可以利用设备树来自动配置和加载,从而简化了驱动的编写和维护。
总的来说,LED驱动程序框架提供了标准的方式来管理和控制LED硬件,使得开发者无需关注底层的具体硬件细节,只需按照框架规定的方式编写驱动代码即可实现LED的控制功能。
第4讲 Linux LED灯驱动实验(直接操作寄存器)_笔记
一、地址映射
1、裸机LED灯实验就是操作6ULL的寄存器。
2,Linux驱动开发也可以操作寄存器,Linux不能直接对寄存器物理地址进行读写操作,比如寄存器A物理地址为0X01010101。
裸机的时候可以直接对0X01010101这个物理地址进行操作,但是linux下不行。
因为linux会使能MMU。
在linux里面操作的都是虚拟地址,所以需要先得到0X01010101这个物理地址对应的虚拟地址。
获得物理物理地址对应的虚拟地址使用ioremap函数。
第一个参数就是物理地址其实大小,第二个参数就是要转化的字节数量。
0X01010101,开始10个地址进行转换,
va=ioremap(0X01010101, 10).
卸载驱动的时候:
iounmap(va);
二、LED灯字符设备驱动框架搭建
1、uboot下载系统失败,以前都能成功,突然不能下载怎么解决?
首先,保证正个网段内开发板的IP地址和ubuntu的IP地址是唯一的,测试哪个IP地址有冲突,比如ubuntu的192.168.1.66有被其他设备占用,如果有占用就改一个没被占用的IP地址。
三、驱动程序编写
1、初始化时钟、IO、GPIO等等。
2、初始化完成以后进行测试,但是如果你烧写/用的是正点原子提供的linux内核,这个时候LED灯默认被配置为了心跳灯,必须关闭心跳灯。
四、应用程序编写
五、测试
1、加载驱动
2、创建设备节点
mknod /dev/led c 200 0。
第四章 LED驱动程序 Linux设备驱动程序 教学课件
驱动程序的设备注册
在设备驱动程序模块初始化的时候,设备驱动 程序会通过入口点向系统登记一个字符设备的 驱动程序,以便系统在适当的时候调用。
嵌入式Linux系统里,通过调用register-chrdev 向系统注册字符型设备驱动程序。如果操作成 功,设备名就会出现在/proc/devices文件里。
当用户进程利用系统调用对设备文件进行读写操作时, 这些系统调用通过设备的主设备号和次设备号来确定相 应的设备驱动程序,然后读取file_operations中相应的 函数指针,接着把控制权交给函数,从而完成了Linux 设备驱动程序的工作。
字符设备驱动程序的入口
Open入口点
Open函数负责打开设备、准备I/O。 Open函数必须对将要进行I/O操作做好必要的准备工
I/O端口
和硬件打交道不能离开I/O端口,以前ISA设备 是占用实际的I/O端口,而在嵌入式Linux下,操 作系统没有把I/O端口屏蔽掉。换句话说,任何 驱动程序都可对任意的I/O端口进行操作,这样 往往很容易引起混乱,不便于管理。所以,每 个驱动程序都应该避免使用端口时发生冲突。
为了避免误用端口,导致系统崩溃。在使用I/O 端口前,应该首先检查此I/O端口是否已有别的 程序在使用,如果没有,再把此端口标记为正 在使用,使用完成以后释放端口。
Write入口点
Write函数负责往设备上写数据。有缓冲区的I/O设备 操作一般是把数据写入到缓冲区里。
Ioctl入口点
Ioctl函数执行读、写之外的操作,主要实现对设备的 控制。
字符设备驱动程序的入口
Select入口点
Linux驱动开发之LED驱动
Linux驱动开发之LED驱动⾸先讲下字符设备控制技术:⼤部分驱动程序除了需要提供读写设备的能⼒外,还需要具备控制设备的能⼒。
⽐如: 改变波特率。
在⽤户空间,使⽤ioctl系统调⽤来控制设备,原型如下:int ioctl(int fd,unsigned long cmd,...)fd: 要控制的设备⽂件描述符cmd: 发送给设备的控制命令…: 第3个参数是可选的参数,存在与否是依赖于控制命令(第 2 个参数 )。
当应⽤程序使⽤ioctl系统调⽤时,驱动程序将由如下函数来响应:2.6.36 之前的内核:long (*ioctl) (struct inode* node ,struct file* filp, unsigned int cmd,unsigned long arg)2.6.36 之后的内核:long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)参数cmd: 通过应⽤函数ioctl传递下来的命令命令从其实质⽽⾔就是⼀个整数, 但为了让这个整数具备更好的可读性,我们通常会把这个整数分为⼏个段:类型(8位),序号,参数传送⽅向,参数长度。
Type(类型/幻数):表明这是属于哪个设备的命令。
Number( ):序号,⽤来区分同⼀设备的不同命令Direction:参数传送的⽅向,可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE(向设备写⼊参数)Size:参数长度Linux系统提供了下⾯的宏来帮助定义命令:_IO(type,nr):不带参数的命令_IOR(type,nr,datatype):从设备中读参数的命令_IOW(type,nr,datatype):向设备写⼊参数的命令例:#define MEM_MAGIC ‘m’ //定义幻数#define MEM_SET _IOW(MEM_MAGIC, 0, int)unlocked_ioctl函数的实现通常是根据命令执⾏的⼀个switch语句。
嵌入式实验四(Linux 内核移植及 LED 驱动测试)
实验四 Linux 内核移植及 LED 驱动测试一、实验目的:1.熟悉 Linux 内核基本目录结构,为后续 Linux 底层开发做准备,熟悉 Linux 内核的配置及编译过程。
2.了解嵌入式 Linux 驱动开发基本方法,熟悉嵌入式 Linux 字符设备驱动的开发框架。
二、实验内容:1.下载或拷贝 Linux-3.14 源码。
2.针对实验箱配置内核。
3.编译内核并测试。
4.利用 Exynos4412 的 GPX2_7、GPX1_0、GPX2_4、GPX3_0 这 4个 I/O 引脚控制 4 个 LED 发光二极管,使其闪烁。
三、实验原理:1.Linux内核是Linux操作系统的核心,也是整个Linux功能体现。
它是用C语言编写,符合POSIX标准。
Linux最早是由芬兰黑客Linus Torvalds为尝试在英特尔X86架构上提供自由免费的类Unix操作系统而开发的。
该计划开始于1991年,这里有一份Linus Torvalds当时在Usenet新闻组comp.os.minix所登载的帖子,这份著名的帖子标志着Linux计划的正式开始。
在计划的早期有一些Minix黑客提供了协助,而今天全球无数程序员正在为该计划无偿提供帮助。
Linux内核源代码非常庞大,随着版本的发展不断增加。
它使用1目录树结构,并且使用Makefile组织配置编译。
顶层目录的Makefile 是整个内核配置编译的核心文件,负责组织目录树中子目录的编译管理,还可以设置体系结构和版本号等。
嵌入式系统中内核移植需根据具体硬件配置对内核源码进行相应地修改、配置。
2. 如图所示,LED2~LED5分别与GPX2_7、GPX1_0、GPX2_4、GPF3_5相连,通过GPX2_7、GPX1_0、GPX2_4、GPX3_0引脚的高低电平来控制三极管的导通性,从而控制LED的亮灭。
当这几个引脚输出高电平时发光二极管点亮;反之,发光二极管熄灭。
Linux及LED驱动实验0801
Linux简介Linux是一种自由和开放源码的类Unix操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。
Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。
Linux是一个领先的操作系统,世界上运算最快的10台超级计算机运行的都是Linux操作系统。
严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。
Linux得名于天才程序员林纳斯·托瓦兹。
Linux操作系统是UNIX操作系统的一种克隆系统,它诞生于1991 年的10 月5 日(这是第一次正式向外公布的时间)。
以后借助于Internet网络,并通过全世界各地计算机爱好者的共同努力,已成为今天世界上使用最多的一种UNIX 类操作系统,并且使用人数还在迅猛增长。
[1]Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。
它能运行主要的UNIX工具软件、应用程序和网络协议。
它支持32位和64位硬件。
Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
它主要用于基于Intel x86系列CPU的计算机上。
这个系统是由全世界各地的成千上万的程序员设计和实现的。
其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。
[2]Linux以它的高效性和灵活性著称,Linux模块化的设计结构,使得它既能在价格昂贵的工作站上运行,也能够在廉价的PC机上实现全部的Unix特性,具有多任务、多用户的能力。
Linux是在GNU公共许可权限下免费获得的,是一个符合POSIX标准的操作系统。
Linux操作系统软件包不仅包括完整的Linux操作系统,而且还包括了文本编辑器、高级语言编译器等应用软件。
Linux LED点灯驱动程序
Linux LED点灯驱动程序目录Linux LED点灯驱动程序 (1)1.1 原理 (1)1.2 实验步骤 (1)⑴编写led.c文件 (1)⑵编写Makefile文件 (6)⑶编译 (6)⑷加载模块 (6)⑸编写测试文件led_test.c (6)⑹编译测试程序 (7)⑺运行测试程序 (7)1.1 原理(1)LED 接口电路由于单只LED 管的工作电压低(大约在 1.5~2V),个别需达到4V,同时工作电流仅为1~5mA,因此可以用CPU 的通用输入输出管脚(GPIO)直接控制LED 的亮灭。
LED 的接口电路如下图1所示:图1 LED接口电路1.2 实验步骤⑴编写led.c文件①建立led目录:#mkdir /gdut2410/led②进入led目录,在该目录下建立两个子目录driver 和test ,前者用来存放驱动程序,后者用来存放驱动测试程序:#cd /gdut2410/led#mkdir driver test③进入驱动程序目录,建立设备驱动文件led.c:cd drivergedit led.cLED 驱动程序如代码5-1 所示:代码清单5-1 LED驱动程序led.c//***************************** 头文件******************************** #include <linux/kernel.h>#include <linux/module.h>#include <linux/device.h>#include <linux/types.h>#include <linux/ioctl.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <linux/fs.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>//*********************** 定义设备结构体及相关宏********************** #define DEVICE_NAME "led" //定义设备名#define DEVICE_MAJOR 212 //手动定义LED 设备的主设备号为212 static int led_major = DEVICE_MAJOR ;#define LED1 S3C2410_GPF4 //定义LED1 对应S3C2410 的GPF4 端口#define LED1_OUTP S3C2410_GPF4_OUTP#define LED_ON 0 //给端口低电平(0)时,LED 亮#define LED_OFF 1 //给端口高电平(1)时,LED 灭//定义LED 设备结构体struct s3c2410_led_dev{struct cdev cdev; //LED 设备对应一个字符设备结构体int status; //LED 状态标识,0 代表灭,1 代表亮};static struct s3c2410_led_dev dev;//***************************** 函数声明******************************** void s3c2410_led_InitIO(void); //初始化IO 端口的函数//***************************** 函数定义******************************** /*================================================================== s3c2410_led _InitIO()描述: 初始化IO 端口参数: 无返回值: 无================================================================== */void s3c2410_led_InitIO(void){int i;//配置LED 对应的端口为输出s3c2410_gpio_cfgpin(LED1, LED1_OUTP);//配置LED 初始为熄灭状态s3c2410_gpio_setpin(LED1, LED_OFF);}/*================================================================== s3c2410_led_open()描述: 打开设备参数:返回值: 0================================================================== */static int s3c2410_led_open(struct inode *inode,struct file *filp){return 0;}/*================================================================== s3c2410_led_release()描述: 注销设备参数:返回值: 0================================================================== */static int s3c2410_led_release(struct inode *inode,struct file *filp){return 0;}/*================================================================== s3c2410_led_ioctl()描述: IO 控制,通过LED_ON 和LED_OFF命令控制LED 的亮灭参数: cmd:用户控制命令,包括LED_ON 和LED_OFF返回值: 0================================================================== */static int s3c2410_led_ioctl(struct inode *inode,struct file *filp, unsigned int cmd,unsigned long arg){switch(cmd) {case LED_ON:s3c2410_gpio_setpin(LED1, LED_ON);dev.status = 1;break;case LED_OFF:s3c2410_gpio_setpin(LED1, LED_OFF);dev.status = 0;break;default:return -EINV AL;}return 0;}/*================================================================== s3c2410_led_read()描述: 读,读取LED 的状态参数: buffer: 用来存储读取的LED 状态;count: 用来记录用户读取了多少个字符返回值: count================================================================== */static ssize_t s3c2410_led_read(struct file *filp,char *buffer, ize_t count,loff_t *ppos){put_user(dev.status,(int *)buffer); //读取LED 状态return 1;}/*================================================================== s3c2410_led_write()描述: 写操作函数,本实例中不做任何事参数:返回值: count================================================================== */static ssize_t s3c2410_led_write(struct file *filp,char *buffer, size_t count,loff_t *ppos) {get_user(dev.status,(int *)buffer);if(dev.status == 0) //灭s3c2410_gpio_setpin(LED1, LED_OFF);else if(dev.status == 1)//亮s3c2410_gpio_setpin(LED1, LED_ON);return 1;}/*================================================================== s3c2410_led_fops描述: 文件操作结构体,实现s3c2410_button_open()等函数与open()等系统调用的连接参数: 无返回值: 无================================================================== */static struct file_operations s3c2410_led_fops = {.owner = THIS_MODULE,.open = s3c2410_led_open,.release = s3c2410_led_release,.ioctl = s3c2410_led_ioctl,.read = s3c2410_led_read,.write = s3c2410_led_write,};/*================================================================== led_setup_cdev()描述: 安装LED 设备的功能函数,在设备加载模块里面调用参数: 无返回值: 无================================================================== */static void led_setup_cdev(void){int err ,devno = MKDEV (led_major , 0);cdev_init(&dev.cdev,&s3c2410_led_fops);dev.cdev.owner = THIS_MODULE;dev.cdev.ops = &s3c2410_led_fops; //建立设备文件操作与系统调用之间的连接err = cdev_add(&dev.cdev,devno,1); //向系统添加该设备if(err)printk("Error %d adding LED %d",err);}/*================================================================== s3c2410_led_init()描述: 模块加载,IO 及相关变量初始化参数: 无返回值: 无================================================================== */static int s3c2410_led_init(void){int result;dev_t devno = MKDEV(led_major,0);//根据主设备号得到dev_t类型的设备号devno if(led_major) //如果手动分配了主设备号result = register_chrdev_region(devno,1,"DEVICE_NAME"); //向系统申请该设备号else{ //否则动态获取设备号result = alloc_chrdev_region(&devno ,0 ,1,"DEVICE_NAME");led_major = MAJOR(devno);}if(result < 0)return result;led_setup_cdev(); // 注册LED 设备s3c2410_led_InitIO(); // 初始化IO 端口// initialize the vals;dev.status = 0; //LED 的初始状态是灭printk(DEVICE_NAME " initialized\n");return 0;}/*================================================================== s3c2410_led_exit()描述: 模块卸载函数参数: 无返回值: 无================================================================== */static void s3c2410_led_exit(void){cdev_del(&dev.cdev); //注销设备unregister_chrdev_region(MKDEV(led_major,0),1); //释放设备号}module_init(s3c2410_led_init);module_exit(s3c2410_led_exit);MODULE_LICENSE("GPL"); //设备许可⑵编写Makefile文件在主机的/gdut2410/led/driver 目录下#cd /gdut2410/led/driver# vi Makefile在该文件中加入以下内容:(其中KDIR 是内核目录,读者要根据自己的内核所在目录来设置)obj-m := led.oKDIR := /gdut2410/kernel/linux-2.6.24PWD := $(shell pwd)default:$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules⑶编译#make编译完成后,该目录下会生成led.ko文件,该文件就是编译成功的模块文件,然后把led.ko拷贝到nfs共享目录。
linux驱动学习笔记LED
LED驱动学习:是一个char字符类型的驱动//配置模式为输出端口static unsigned int led_cfg_table [] = {S3C2410_GPB5_OUTP,S3C2410_GPB6_OUTP,S3C2410_GPB7_OUTP,S3C2410_GPB8_OUTP,};s3c2410_gpio_cfgpin(S3C2410_GPB5, S3C2410_GPB5_OUTP);s3c2410_gpio_cfgpin(37, 0x01 << 10);这个在\arch\arm\mach-s3c2410\include\mach\regs-gpio.h中定义#define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPB5_INP (0x00 << 10)#define S3C2410_GPB5_OUTP (0x01 << 10)#define S3C2410_GPB5_nXBACK (0x02 << 10)S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))#define S3C2410_GPIO_BANKA (32*0)#define S3C2410_GPIO_BANKB(32*1)static int __init dev_init(void){int ret;int i;for (i = 0; i < 4; i++) {s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);s3c2410_gpio_setpin(led_table[i], 0);}在驱动的初始化函数中经常看到,__init 前缀,这个在下面文件中定义file:/include/linux/init.h• /* These macros are used to mark some functions or•* initialized data (doesn't apply to uninitialized data)•* as `initialization' functions. The kernel can take this•* as hint that the function is used only during the initialization •* phase and free up used memory resources after•*•* Usage:•* For functions:•*•* You should add __init immediately before the function name, like: •*•* static void __init initme(int x, int y)•* {•* extern int z; z = x * y;•* }主要是将这个函数放在init段section中,这样可以在执行完成后,释放内存。
LED驱动
Linux系统下的LED驱动(X86平台)1.Linux设备驱动与整个软硬件系统的关系如图所示,除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数即可访问字符设备和块设备。
所有的字符设备和块设备都被统一地呈现给用户。
块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如FA T、Ext3、Y AFFS、JFFS等。
2. Linux系统下的LED驱动硬件环境:PC104软件环境:使用的系统为红旗Linux Notebook 5.0(内核版本Linux 2.6.16.9-9smp SMP PENTIUM gcc-3.4)编译的内核为2.6.16(注意版本的匹配,前面三位要一致)Linux提供了这样一种机制——模块。
模块具有以下特点:1 模块本身不被编译进内核2 模块一旦被加载,它就和内核中的其他部分完全一样。
先看一个最简单的内核模块“Hello World”,代码如下所示#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE(“Dual BSD/GPL”);static int hello_init(void){printk(KERN_ALERT “Hello World enter\n”);return 0;}static void hello_exit(void){printk(KERN_ALERT “Hello World exit\n”);}module_init(hello_init);module_eixt(hello_exit);这个最简单的内核模块只包含内核加载函数、卸载函数和对GPL许可权限的声明以及一些描述信息。
编译它会产生hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello World exit”。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第五章:Linux驱动介绍5.1 驱动原理:LINUX提供标准接口函数给底层,底层驱动按照LINUX编程规则进行驱动编写。
操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。
设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,在Linux内核源程序中也占有60%以上。
因此,熟悉驱动的编写是很重要的.Linux内核中采用可加载的模块化设计(LKMs,Loadable Kernel Modules),一般情况下编译的Linux内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中,其他的代码可以编译到内核中,或者编译为内核的模块文件(在需要时动态加载)。
5.2 内核模块的主要相关命令◆lsmod:列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块大小,第三列则是使用该模块的对象数目。
◆rmmod:是用于将当前模块卸载。
◆insmod和modprobe是用于加载当前模块,但insmod不会自动解决依存关系,即如果要加载的模块引用了当前内核符号表中不存在的符号,则无法加载,也不会去查在其他尚未加载的模块中是否定义了该符号;modprobe可以根据模块间依存关系以及/etc/modules.conf文件中的内容自动加载其他有依赖关系的模块。
5.3 设备分类Linux系统的设备分为三类:字符设备--(包含一个混杂设备)、块设备和网络设备。
字符设备通常指像普通文件或字节流一样,以字节为单位顺序读写的设备,如并口设备、虚拟控制台等。
字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针),而大多数字符设备只能提供顺序访问,因为对它们的访问不会被系统所缓存。
但也有例外,例如帧缓存(framebuffer)是一个可以被随机访问的字符设备。
块设备通常指一些需要以块为单位随机读写的设备,如IDE硬盘、SCSI硬盘、光驱等。
块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、闪存等)。
Linux可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同。
$ ls –l /devcrw-rw---- 1 root uucp 4, 64 08-30 22:58 ttyS0 /*串口设备, c表示字符设备*/ brw-r----- 1 root disk 8, 0 08-11 23:03 sda /*硬盘设备,b表示块设备*/ 5.4 设备驱动程序工作原理模块在调用insmod命令时被加载,此时的入口点是init_module()函数,通常在该函数中完成设备的注册。
同样,模块在调用rmmod命令时被卸载,此时的入口点是cleanup_module()函数,在该函数中完成设备的卸载。
在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作。
5.5 应用程序、库、内核、驱动程序的关系:4层软件关系如下:1.应用程序通过open函数打开设备文件;2.库根据open函数执行swi中断,引起异常进入内核;3.内核根据异常相关参数(应用程序传递的)找到相应驱动程序,并返回一文件句柄给库;4.库根据文件句柄,触发库提供的write或ioclt函数(函数相关参数由应用程序提供)执行swi触发异常后进入内核;5.内核根据传递的相关参数调用驱动程序相关函数进行相关操作,如点亮led等。
5.6 LINUX驱动程序开发步骤:1.查看原理图、数据手册,了解设备的操作方法;2.在内核中找到相近的驱动程序,以它为模块进行开发,有时候需要从零开始;3.实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序;4.设计所要实现的操作,比如open、close、read、write等函数;5.实现中断服务(中断并不是每个设备驱动所必须的)。
6.编译该驱动程序到内核中,或者用insmod命令加载;7.测试驱动程序。
5.7 驱动程序的加载和卸载:可以将驱动程序静态编译进内核中,也可以将它作为模块在使用时再加载。
在配置内核时,如果某个配置项设为m,就表示它将会被编译成一个模块。
在linux2.6内核中,模块的扩展名为.ko,可以使用insmod 命令加载,使用rmmod命令卸载,使用lsmod命令查看内核中已经加载了哪些模块。
当使用insmod加载模块时,模块的初始化函数被调用,它用来向内核注册驱动程序;当使用rmmod卸载模块时,模块的清除函数被调用。
在驱动代码中,这两个函数要么取固定的名字:init_module和5.8关键概念5.8.1 不可剥夺型(non-preemptive kernel):(分时操作系统内核)要求每个任务主动放弃CPU的使用权。
优点:响应中断快;几乎无须使用信号量保护共享数据,运行中的任务占有CPU,而不必担心被别的任务抢占。
缺点:响应时间,高优先级的任务已经进入就绪态,但还不能运行,要等,直到当前运行着的任务释放CPU。
5.8.2 可剥夺型内核(preemptive kernel):(实时操作系统内核)最高优先级的任务一旦就绪,总能得到CPU的使用权。
当一个运行着的任务使一个比它优先级高的任务进入就绪态时,当前任务的CPU使用权就被剥夺了,或者说被挂起了,更高优先级的任务立刻得到了CPU的使用权。
如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的任务开始运行。
可剥夺型内核使得任务级响应时间得以最优化。
5.8.3 可重入函数:可以被一个以上的任务调用,而不必担心数据被破坏。
可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。
可重入函数或者只使用局部变量,即变量保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。
(一个函数被多个任务调用时,每个任务都有自己独立的栈函数存放该函数运行的中间变量)。
5.8.4 资源:任何被任务所占用的实体都可称为资源。
资源可以是输入/输出设备,也可以是一个变量,一个结构或一个数组。
5.8.5 共享资源:可以被一个以上使用的资源叫做共享资源。
为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源。
这叫做互斥(mutual exclusion)。
5.8.6 代码的临界段也称为临界区,指处理时不可分割的代码。
一旦这部分代码开始执行,则不允许任何中断打入。
为确保临界段代码的执行不被中断,在进入临界段之前必须关中断,而临界段代码执行完后,要立即开中断。
5.8.7 实时系统的特点如果逻辑和时序出现偏差,将会引起严重后果。
有两种类型的实时系统:1.软实时系统:系统的宗旨是使各个任务尽快的运行,而不要求限定某一任务在多长时间内完成;如uc/os操作系统。
2.硬实时系统:各个任务不仅须执行无误,而且要做到准时。
像VxWorks通过硬件完成(例如通过定时器等方式),优先级高任务先执行,且每个任务执行时间可以指定。
大多数实时系统是两者的结合。
如uc/os中可以通过中断发生来改变任务运行状态(任务切换)就是一种硬件机制来完成。
5.8.8 死锁:也称为抱死(deadlock或deadly embrace),指两个任务无限期的相互等待对方控制着的资源。
最简单的防止死锁的方法,让每个任务都:(1)先得到全部需要的资源,再做下一步的工作;(2)用同样的顺序申请多个资源;(3)释放资源时,使用相反的顺序(按啥顺序得到,就按啥顺序释放,和中断进栈、出栈类似)。
内核通过定义等待超时来化解死锁-当等待时间超过了某一确定值,而信号量还是无效状态时,就会返回某种形式的出现超时错误的代码。
这个出错代码告知该任务,不是得到了资源使用权,而使系统错误。
5.8.9 Linux将进程状态描述为如下五种:TASK_RUNNING:可运行状态。
处于该状态的进程能被调度执行而成为当前进程。
TASK_INTERRUPTIBLE:可中断的睡眠状态。
处于该状态的进程在所需资源有效时被唤醒,也能通过信号或定时中断唤醒。
TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。
处于该状态的进程仅当所需资源有效时被唤醒。
TASK_ZOMBIE:僵尸状态。
表示进程结束且已释放资源,但其task_struct(任务结构体)仍未释放。
相当于进程已经结束,但在内核的任务表里面还有相应的结构资源。
TASK_STOPPED:暂停状态。
处于该状态的进程通过其他进程的信号才能被唤醒。
如果处于暂停状态的进程被其它进程唤醒,但资源没有到位,那么就进入睡眠状态。
5.8.10 linux内核的三种调度方法:1.SCHED_OTHER 分时调度策略;(各任务时间片平均分配,linux2.4以前的内核)2.SCHED_FIFO实时调度策略,先到先服务;(各任务谁优先级高,先运行谁,linux改进版本,要付费的)3.SCHED_RR实时调度策略,时间片轮转(同优先级就可以按时间分片,不是同优先级按实时调度)(如linux2.6版本,半实时操作系统)。
实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice(优先级)和counter(个数,时间片)值决定权值,nice越小(优先级越高),counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。
SHCED_RR和SCHED_FIFO的不同:当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。
放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
SCHED_FIFO一旦占用cpu则一直运行。
一直运行直到有更高优先级任务到达或自己放弃。
如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。
而RR可以让每个任务都执行一段时间。
相同点:1)RR和FIFO都只用于实时任务。
2)创建时优先级大于0(1-99)。
3)按照可抢占优先级调度算法进行。
4)就绪态的实时任务立即抢占非实时任务。
(SCHED_FIFO是实时,一个任务在执行,相同优先级其它任务不能执行;SHCED_RR是实时,同优先级协商式,一个任务在执行,相同优先级可以同时以时间片方式执行)。
5.8.11 所有任务都采用linux分时调度策略时:1.创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。