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
计算机基础知识:linux系统怎么安装?
在虚拟机软件中创建一个新的虚拟机,并选择相应操作系统类型和硬盘空间大小等参数。将下载的 ISO 镜像文件加载到虚拟机中。启动虚拟机,进入安装界面,并按照步骤进行安装。安装完成后,可以在虚拟机中使用 Linux 系统。
|
22天前
|
缓存 监控 Linux
Linux系统清理缓存(buff/cache)的有效方法。
总结而言,在大多数情形下你不必担心Linux中buffer与cache占用过多内存在影响到其他程序运行;因为当程序请求更多内存在没有足够可用资源时,Linux会自行调整其占有量。只有当你明确知道当前环境与需求并希望立即回收这部分资源给即将运行重负载任务之前才考虑上述方法去主动干预。
307 10
|
26天前
|
安全 Linux 数据安全/隐私保护
为Linux系统的普通账户授予sudo访问权限的过程
完成上述步骤后,你提升的用户就能够使用 `sudo`命令来执行管理员级别的操作,而无需切换到root用户。这是一种更加安全和便捷的权限管理方式,因为它能够留下完整的权限使用记录,并以最小权限的方式工作。需要注意的是,随意授予sudo权限可能会使系统暴露在风险之中,尤其是在用户不了解其所执行命令可能带来的后果的情况下。所以在配置sudo权限时,必须谨慎行事。
181 0
|
1月前
|
Ubuntu Linux 开发者
国产 Linux 发行版再添新成员,CutefishOS 系统简单体验
当然,系统生态构建过程并不简单,不过为了帮助国产操作系统优化生态圈,部分企业也开始用国产操作系统替代 Windows,我们相信肯定会有越来越多的精品软件登录 Linux 平台。
77 0
|
1月前
|
Ubuntu 安全 Linux
Linux系统入门指南:从零开始学习Linux
Shell脚本是一种强大的自动化工具,可以帮助您简化重复的任务或创建复杂的脚本程序。了解Shell脚本的基本语法和常用命令,以及编写和运行Shell脚本的步骤,将使您更高效地处理日常任务。
122 0
|
1月前
|
Ubuntu Linux 图形学
Linux学习之Linux桌面系统有哪些?
Cinnamon:与MATE类似,Cinnamon 拥有 GNOME 和 Unity 等其它桌面环境所没有的种种功能,是高度可定制的桌面环境,不需要任何外部插件、窗口组件和调整工具来定制桌面。
80 0
|
1月前
|
Ubuntu 安全 Linux
十款常用Linux系统介绍
本文不是什么大盘点。市面上有好几百款发行版,每款发行版在某个方面都与众不同。不可能在此全部罗列,本文只罗列了十款最常见的Linux发行版(世界上只有两种人,一种是懂二进制的,另一种是不懂二进制的)。请宣传Linux的魅力或威力。
|
1月前
|
Ubuntu 安全 Linux
linux系统|Ubuntu 18.10 如期正式发布,新面孔新技术都来了
微软公司也终于沉不住气要在linux开源系统开疆扩土了。mscode这样的工具的确好用,虽然差第一名那么一点儿,但是最老版的公司出版的软件的确很是让人动心!
|
1月前
|
Ubuntu Linux 数据安全/隐私保护
Win10安装Linux子系统教程!如何在Win10系统中安装Ubuntu!
登录系统后,输入cd /返回上一级,然后再输入“ls”查看一下系统文件目录,看看对不对!
|
1月前
|
Web App开发 安全 Linux
Linux 比起其他系统的5 个优点和 5 个缺点
对Linux系统感兴趣的朋友,可以点击下方书籍进行学习。

热门文章

最新文章