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


相关文章
|
1月前
|
Linux 定位技术
Linux系统中的cd命令:目录切换技巧
踏过千山,越过万水,人生就是一场不断前行的旅程,总充满了未知与挑战。然而,“cd”命令如同你的旅伴,会带你穿梭在如棋盘一般的文件系统中,探索每一处未知。希望你能从“cd”命令中找到乐趣,像是掌控了一种络新妙的魔法,去向未知进发,开始你的探索之旅。
118 24
|
26天前
|
Linux Shell
Linux系统下快速批量创建和删除文件的方法
总的来说,使用shell脚本来批量处理文件是一种非常强大的工具,只要你愿意花时间学习和实践,你会发现它能大大提高你的工作效率。
81 19
|
1月前
|
安全 Shell Linux
Linux系统之su命令的基本使用
Linux系统之su命令的基本使用
96 3
Linux系统之su命令的基本使用
|
28天前
|
Ubuntu Linux 编译器
在Ubuntu Linux系统下如何搭建并安装EDK2
以上就是在Ubuntu Linux系统下搭建并安装EDK2的过程。这个过程可能会有些复杂,但只要按照步骤一步步来,应该不会有太大问题。如果在过程中遇到任何问题,都可以在网上找到相应的解决方案。希望这个指南能对你有所帮助!
90 17
|
2月前
|
监控 Linux Python
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
128 27
|
2月前
|
Linux
Linux系统ext4磁盘扩容实践指南
这个过程就像是给你的房子建一个新的储物间。你需要先找到空地(创建新的分区),然后建造储物间(格式化为ext4文件系统),最后将储物间添加到你的房子中(将新的分区添加到文件系统中)。完成这些步骤后,你就有了一个更大的储物空间。
198 10
|
3月前
|
Linux
Linux系统之whereis命令的基本使用
Linux系统之whereis命令的基本使用
151 24
Linux系统之whereis命令的基本使用
|
2月前
|
存储 缓存 Linux
Linux系统中如何查看CPU信息
本文介绍了查看CPU核心信息的方法,包括使用`lscpu`命令和读取`/proc/cpuinfo`文件。`lscpu`能快速提供逻辑CPU数量、物理核心数、插槽数等基本信息;而`/proc/cpuinfo`则包含更详细的配置数据,如核心ID和处理器编号。此外,还介绍了如何通过`lscpu`和`dmidecode`命令获取CPU型号、制造商及序列号,并解释了CPU频率与缓存大小的相关信息。最后,详细解析了`lscpu`命令输出的各项参数含义,帮助用户更好地理解CPU的具体配置。
241 8
|
2月前
|
存储 运维 监控
深度体验阿里云系统控制台:SysOM 让 Linux 服务器监控变得如此简单
作为一名经历过无数个凌晨三点被服务器报警电话惊醒的运维工程师,我对监控工具有着近乎苛刻的要求。记得去年那次大型活动,我们的主站流量暴增,服务器内存莫名其妙地飙升到90%以上,却找不到原因。如果当时有一款像阿里云 SysOM 这样直观的监控工具,也许我就不用熬通宵排查问题了。今天,我想分享一下我使用 SysOM 的亲身体验,特别是它那令人印象深刻的内存诊断功能。
|
2月前
|
存储 NoSQL Linux
微服务2——MongoDB单机部署4——Linux系统中的安装启动和连接
本节主要介绍了在Linux系统中安装、启动和连接MongoDB的详细步骤。首先从官网下载MongoDB压缩包并解压至指定目录,接着创建数据和日志存储目录,并配置`mongod.conf`文件以设定日志路径、数据存储路径及绑定IP等参数。之后通过配置文件启动MongoDB服务,并使用`mongo`命令或Compass工具进行连接测试。此外,还提供了防火墙配置建议以及服务停止的两种方法:快速关闭(直接杀死进程)和标准关闭(通过客户端命令安全关闭)。最后补充了数据损坏时的修复操作,确保数据库的稳定运行。
115 0