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月前
|
Ubuntu Linux Anolis
Linux系统禁用swap
本文介绍了在新版本Linux系统(如Ubuntu 20.04+、CentOS Stream、openEuler等)中禁用swap的两种方法。传统通过注释/etc/fstab中swap行的方式已失效,现需使用systemd管理swap.target服务或在/etc/fstab中添加noauto参数实现禁用。方法1通过屏蔽swap.target适用于新版系统,方法2通过修改fstab挂载选项更通用,兼容所有系统。
136 3
Linux系统禁用swap
|
1月前
|
Linux
Linux系统修改网卡名为eth0、eth1
在Linux系统中,可通过修改GRUB配置和创建Udev规则或使用systemd链接文件,将网卡名改为`eth0`、`eth1`等传统命名方式,适用于多种发行版并支持多网卡配置。
185 3
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
156 0
Linux系统初始化脚本
|
2月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
155 18
|
1月前
|
安全 Linux Shell
Linux系统提权方式全面总结:从基础到高级攻防技术
本文全面总结Linux系统提权技术,涵盖权限体系、配置错误、漏洞利用、密码攻击等方法,帮助安全研究人员掌握攻防技术,提升系统防护能力。
150 1
|
1月前
|
监控 安全 Linux
Linux系统提权之计划任务(Cron Jobs)提权
在Linux系统中,计划任务(Cron Jobs)常用于定时执行脚本或命令。若配置不当,攻击者可利用其提权至root权限。常见漏洞包括可写的Cron脚本、目录、通配符注入及PATH变量劫持。攻击者通过修改脚本、创建恶意任务或注入命令实现提权。系统管理员应遵循最小权限原则、使用绝对路径、避免通配符、设置安全PATH并定期审计,以防范此类攻击。
616 1
|
2月前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
980 10
|
2月前
|
安全 Linux 数据安全/隐私保护
为Linux系统的普通账户授予sudo访问权限的过程
完成上述步骤后,你提升的用户就能够使用 `sudo`命令来执行管理员级别的操作,而无需切换到root用户。这是一种更加安全和便捷的权限管理方式,因为它能够留下完整的权限使用记录,并以最小权限的方式工作。需要注意的是,随意授予sudo权限可能会使系统暴露在风险之中,尤其是在用户不了解其所执行命令可能带来的后果的情况下。所以在配置sudo权限时,必须谨慎行事。
397 0
|
2月前
|
Ubuntu Linux 开发者
国产 Linux 发行版再添新成员,CutefishOS 系统简单体验
当然,系统生态构建过程并不简单,不过为了帮助国产操作系统优化生态圈,部分企业也开始用国产操作系统替代 Windows,我们相信肯定会有越来越多的精品软件登录 Linux 平台。
153 0
|
2月前
|
Ubuntu 安全 Linux
Linux系统入门指南:从零开始学习Linux
Shell脚本是一种强大的自动化工具,可以帮助您简化重复的任务或创建复杂的脚本程序。了解Shell脚本的基本语法和常用命令,以及编写和运行Shell脚本的步骤,将使您更高效地处理日常任务。
255 0