Linux底层驱动社区饮水机系统详解

简介: 在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:

 驱动讲解:

在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);

image.gif

2. 出口函数(Cleanup Function)

出口函数,或者称为清理函数,是在驱动程序从内核卸载时被调用的函数。它的目的是释放入口函数中分配的所有资源,如注销设备、释放内存等,确保驱动程序卸载后不会留下任何残留影响。出口函数通过module_exit()宏来标记。例如:

C

1static void __exit my_driver_exit(void)
2{
3    // 释放入口函数中分配的所有资源
4}
5module_exit(my_driver_exit);

image.gif

3. 声明许可证(License Declaration)

声明许可证是为了告知内核和其他开发者,该驱动程序遵循哪种开源许可协议。这很重要,因为它关系到驱动程序的使用、修改和分发的合法性。在驱动程序中,通常通过MODULE_LICENSE()宏来声明。最常见的许可证之一是GNU General Public License (GPL),例如:

C

1MODULE_LICENSE("GPL");

image.gif

综合示例

一个简单的驱动模块初始化部分可能看起来像这样:

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");

image.gif

通过这三个步骤,驱动程序能够被正确地加载到内核中、执行其功能,并在不再需要时干净地卸载,同时遵守开源社区的许可规定。

功能实现:

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");

image.gif

应用层:

#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;
}

image.gif


相关文章
|
11天前
|
Linux 应用服务中间件 Shell
linux系统服务二!
本文详细介绍了Linux系统的启动流程,包括CentOS 7的具体启动步骤,从BIOS自检到加载内核、启动systemd程序等。同时,文章还对比了CentOS 6和CentOS 7的启动流程,分析了启动过程中的耗时情况。接着,文章讲解了Linux的运行级别及其管理命令,systemd的基本概念、优势及常用命令,并提供了自定义systemd启动文件的示例。最后,文章介绍了单用户模式和救援模式的使用方法,包括如何找回忘记的密码和修复启动故障。
31 5
linux系统服务二!
|
11天前
|
Linux 应用服务中间件 Shell
linux系统服务!!!
本文详细介绍了Linux系统(以CentOS7为例)的启动流程,包括BIOS自检、读取MBR信息、加载Grub菜单、加载内核及驱动程序、启动systemd程序加载必要文件等五个主要步骤。同时,文章还对比了CentOS6和CentOS7的启动流程图,并分析了启动流程的耗时。此外,文中还讲解了Linux的运行级别、systemd的基本概念及其优势,以及如何使用systemd管理服务。最后,文章提供了单用户模式和救援模式的实战案例,帮助读者理解如何在系统启动出现问题时进行修复。
32 3
linux系统服务!!!
|
1天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
10 3
|
1天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
8 3
|
4天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
15 6
|
1天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
9 2
|
4天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
21 5
|
5天前
|
机器学习/深度学习 自然语言处理 Linux
Linux 中的机器学习:Whisper——自动语音识别系统
本文介绍了先进的自动语音识别系统 Whisper 在 Linux 环境中的应用。Whisper 基于深度学习和神经网络技术,支持多语言识别,具有高准确性和实时处理能力。文章详细讲解了在 Linux 中安装、配置和使用 Whisper 的步骤,以及其在语音助手、语音识别软件等领域的应用场景。
22 5
|
5天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
19 3
|
5天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####