驱动讲解:
在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:
1. 入口函数(Initialization Function)
入口函数,也称为初始化函数,是驱动程序加载到内核时首先被调用的函数。它的主要职责是初始化驱动所需的各种资源,注册设备,设置中断处理程序(如果适用),以及进行其他必要的配置。在Linux内核中,入口函数通常通过module_init()
宏来标记,该宏背后通常关联着驱动的初始化函数。例如:
C
1static int __init my_driver_init(void) 2{ 3 // 驱动初始化代码,如申请内存、注册设备等 4 return 0; // 成功返回0,失败返回负的错误码 5} 6module_init(my_driver_init);
2. 出口函数(Cleanup Function)
出口函数,或者称为清理函数,是在驱动程序从内核卸载时被调用的函数。它的目的是释放入口函数中分配的所有资源,如注销设备、释放内存等,确保驱动程序卸载后不会留下任何残留影响。出口函数通过module_exit()
宏来标记。例如:
C
1static void __exit my_driver_exit(void) 2{ 3 // 释放入口函数中分配的所有资源 4} 5module_exit(my_driver_exit);
3. 声明许可证(License Declaration)
声明许可证是为了告知内核和其他开发者,该驱动程序遵循哪种开源许可协议。这很重要,因为它关系到驱动程序的使用、修改和分发的合法性。在驱动程序中,通常通过MODULE_LICENSE()
宏来声明。最常见的许可证之一是GNU General Public License (GPL),例如:
C
1MODULE_LICENSE("GPL");
综合示例
一个简单的驱动模块初始化部分可能看起来像这样:
C
1#include <linux/module.h> /* Needed by all modules */ 2#include <linux/kernel.h> /* Needed for KERN_INFO */ 3 4static int __init my_driver_init(void) 5{ 6 printk(KERN_INFO "Loading my_driver module...\n"); 7 // 初始化代码... 8 return 0; /* 初始化成功 */ 9} 10 11static void __exit my_driver_exit(void) 12{ 13 printk(KERN_INFO "Unloading my_driver module...\n"); 14 // 清理代码... 15} 16 17module_init(my_driver_init); 18module_exit(my_driver_exit); 19MODULE_LICENSE("GPL");
通过这三个步骤,驱动程序能够被正确地加载到内核中、执行其功能,并在不再需要时干净地卸载,同时遵守开源社区的许可规定。
功能实现:
1.要实现的功能:
绿灯常亮,接水时红灯亮起。
1.1两个按键实现接水和关水的控制
1.2显示金额的倒计时
1.3接水完成后蜂鸣器响,亮绿灯
代码思路:
首先是驱动的三大步:入口,出口,许可证
1.完成灯和蜂鸣器的初始化
2.接收应用层传输过来的数据
3.实现按键中断,并在其中实现倒计时的显示。
代码实现:
驱动层
#include <linux/init.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/gpio.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/delay.h> #define GPIONO(m, n) m * 32 + n //计算gpio号 #define GPIO_B8 (GPIONO(1, 8)) //计算按键gpio号 #define GPIO_B16 (GPIONO(1, 16)) //计算gpio号 #define NAME "chrdev_led" //led灯驱动名 #define RED_BASE 0xc001a000 //红灯 #define GREEN_BASE 0xc001e000 //绿灯 #define BLUE_BASE 0xc001b000 //蓝灯 #define BEEP_BASE 0xc001c000 //蜂鸣器 unsigned int *red_addr = NULL; unsigned int *green_addr = NULL; unsigned int *blue_addr = NULL; unsigned int *beep_addr = NULL; unsigned int a; //接受应用层出传来的金额 char *temp = NULL; struct timer_list mytimer1; //定时器中断 1 2 3 4 struct timer_list mytimer2; struct timer_list mytimer3; struct timer_list mytimer4; struct file_operations fops; //结构体 int gpiono[] = {GPIO_B8, GPIO_B16}; //数组内存入两个按键的软中断号 char *irqname[] = {"interrupt-b8", "interrupt-b16"}; //中断的名字 int major = 0; //设备号 char buf2[32]; void key_irq_timer_handle(unsigned long data) //定时器中断处理函数1 { *red_addr &= ~(1 << 28); *green_addr |= (1 << 13); *beep_addr |= (1 << 14); mod_timer(&mytimer2, jiffies + 1000); //开启定时器,相当于调用定时器中断函数 } void key_irq_timer_handle_second(unsigned long data) //定时器中断处理函数2 { *red_addr &= ~(1 << 28); *green_addr |= (1 << 13); *beep_addr &= ~(1 << 14); } void key_irq_timer_handle_third(unsigned long data) //定时器中断处理函数3 { //倒计时 int i; int ret = a; *red_addr |= (1 << 28); *green_addr &= ~(1 << 13); for (i = 0; i < ret; i++) { a--; printk("%d\n", a); mdelay(1000); } mod_timer(&mytimer1, jiffies + a * 1000); } void key_irq_timer_handle_four(unsigned long data) //定时器中断处理函数4 { *red_addr &= ~(1 << 28); *green_addr |= (1 << 13); *beep_addr &= ~(1 << 14); } irqreturn_t farsight_irq_handle(int num, void *dev) //按键产生的中断处理函数 { int status_b8 = gpio_get_value(GPIO_B8); //读取gpiob8数值 0 1 int status_b16 = gpio_get_value(GPIO_B16); //读取gpiob16数值 if (status_b8 == 0) { //如果等于0表示按下,执行打印函数 mod_timer(&mytimer3, jiffies + 1); } if (status_b16 == 0) { mod_timer(&mytimer4, jiffies + 1); } return IRQ_HANDLED; } int myopen(struct inode *node_t, struct file *file_t) { return 0; } ssize_t mywrite(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t) { if (sizeof(buf2) < n) n = sizeof(buf2); if (copy_from_user(buf2, ubuf, n) != 0) { printk("copy_from_user error\n"); return -EINVAL; } //将字符串转化为整形数字 temp = buf2; while (*temp >= '0' && *temp <= '9') { a = a * 10 + *temp - '0'; temp++; } return 0; } int myclose(struct inode *node_t, struct file *file_t) { return 0; } struct file_operations fops = { .open = myopen, .write = mywrite, .release = myclose, }; static int __init farsigt_irq_init(void) //入口 { int ret, i; // 注册字符设备驱动 major = register_chrdev(major, NAME, &fops); if (major < 0) { printk("register error\n"); return major; } //1.定时器的申请 mytimer1.expires = jiffies + a * 1000; //时间 mytimer1.function = key_irq_timer_handle; //定时器中断处理函数 mytimer1.data = 0; //参数 init_timer(&mytimer1); //将定时器信息写入进行初始化 add_timer(&mytimer1); //开启一次定时器 mytimer2.expires = jiffies + a * 1000; mytimer2.function = key_irq_timer_handle_second; mytimer2.data = 0; init_timer(&mytimer2); add_timer(&mytimer2); mytimer3.expires = jiffies + a * 1000; mytimer3.function = key_irq_timer_handle_third; mytimer3.data = 0; init_timer(&mytimer3); add_timer(&mytimer3); mytimer4.expires = jiffies + a * 1000; mytimer4.function = key_irq_timer_handle_four; mytimer4.data = 0; init_timer(&mytimer4); add_timer(&mytimer4); //2.中断的申请 for (i = 0; i < ARRAY_SIZE(gpiono); i++) { //这里用for主要目的之申请两个中断 ret = request_irq(gpio_to_irq(gpiono[i]), farsight_irq_handle, IRQF_TRIGGER_FALLING, irqname[i], NULL); //中断申请 参数:软中断号 中断执行函数 下降沿触发 中断的名字 if (ret) { printk("request irq%d error\n", gpio_to_irq(gpiono[i])); //申请失败提示 return ret; } } //转换地址 red_addr = (unsigned int *)ioremap(RED_BASE, 40); if (red_addr == NULL) { printk("ipremap err.\n"); return -EINVAL; } green_addr = (unsigned int *)ioremap(GREEN_BASE, 40); if (green_addr == NULL) { printk("ipremap err.\n"); return -EINVAL; } blue_addr = (unsigned int *)ioremap(BLUE_BASE, 40); if (blue_addr == NULL) { printk("ipremap err.\n"); return -EINVAL; } beep_addr = (unsigned int *)ioremap(BEEP_BASE, 40); if (beep_addr == NULL) { printk("ipremap err.\n"); return -EINVAL; } //灯和蜂鸣器的初始化 *(red_addr + 9) &= ~(3 << 24); *(red_addr + 1) |= (1 << 28); *red_addr &= ~(1 << 28); *(green_addr + 8) &= ~(3 << 26); *(green_addr + 1) |= (1 << 13); *green_addr |= (1 << 13); *(blue_addr + 8) = (0 << 24); *(blue_addr + 8) = (1 << 25); *(blue_addr + 1) |= (1 << 12); *blue_addr &= ~(1 << 12); *(beep_addr + 8) |= (1 << 28); *(beep_addr + 1) |= (1 << 14); *beep_addr &= ~(1 << 14); return 0; } //出口 static void __exit farsight_irq_exit(void) { int i; for (i = 0; i < ARRAY_SIZE(gpiono); i++) { //注销掉中断 free_irq(gpio_to_irq(gpiono[i]), NULL); } del_timer(&mytimer1); //注销掉定时器 del_timer(&mytimer2); //注销掉定时器 del_timer(&mytimer3); //注销掉定时器 del_timer(&mytimer4); //注销掉定时器 iounmap(red_addr); iounmap(green_addr); iounmap(blue_addr); unregister_chrdev(major, NAME); } //驱动三大步 入口 出口 许可证 module_init(farsigt_irq_init); module_exit(farsight_irq_exit); MODULE_LICENSE("GPL");
应用层:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int i; int fd = open("./led", O_RDWR); int ad = open("a.txt", O_RDWR | O_CREAT); char buf[32] = {0}; char buf2[32] = {0}; while (1) { fgets(buf, sizeof(buf), stdin); if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] == '\0'; write(fd, buf, sizeof(buf)); write(ad, buf, strlen(buf)); } close(fd); close(ad); return 0; }