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


相关文章
|
2天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
2天前
|
Linux 网络安全 虚拟化
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
Ngnix04系统环境准备-上面软件是免费版的,下面是收费版的,他更快的原因使用了epoll模型,查看当前Linux系统版本, uname -a,VMWARE建议使用NAT,PC端电脑必须使用网线连接
|
2天前
|
Ubuntu Linux
Linux软件安装-Linux系统靠yum命令安装软件,yum命令是一个RPM包软件管理器,用于自动化安装配置Linux软件,.rpm是Linux包下的软件,yum install下载 wget re
Linux软件安装-Linux系统靠yum命令安装软件,yum命令是一个RPM包软件管理器,用于自动化安装配置Linux软件,.rpm是Linux包下的软件,yum install下载 wget re
|
2天前
|
Linux Windows
Linux01---目录结构,Linux系统下只有一个最顶级的树/,Windows系统有盘符概念,而Linux系统没有盘符概念,整个系统都在/根目录下,Linux 系统写法 /user/local
Linux01---目录结构,Linux系统下只有一个最顶级的树/,Windows系统有盘符概念,而Linux系统没有盘符概念,整个系统都在/根目录下,Linux 系统写法 /user/local
|
2天前
|
安全 Linux 网络安全
部署07--远程连接Linux系统,利用FinalShell可以远程连接到我们的操作系统上
部署07--远程连接Linux系统,利用FinalShell可以远程连接到我们的操作系统上
|
2天前
|
Linux 虚拟化 数据安全/隐私保护
部署05-VMwareWorkstation中安装CentOS7 Linux操作系统, VMware部署CentOS系统第一步,下载Linux系统,/不要忘, CentOS -7-x86_64-DVD
部署05-VMwareWorkstation中安装CentOS7 Linux操作系统, VMware部署CentOS系统第一步,下载Linux系统,/不要忘, CentOS -7-x86_64-DVD
|
2天前
|
Linux 调度
部署03---Linux操作系统的诞生,Linux操作系统由系统的内核和系统的操作系统所组成
部署03---Linux操作系统的诞生,Linux操作系统由系统的内核和系统的操作系统所组成
|
2天前
|
运维 监控 大数据
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
|
2天前
|
Linux 网络安全 开发工具
linux 常用命令【编程必备】
linux 常用命令【编程必备】
14 4
|
2天前
|
存储 Linux
Linux文件的上和下,FinalShell文件右键可下文件,先选择root文件夹,然后把他文件往里面拖动,就可以下载了,命令下载,ls -l可以看当前文件目录,sz 文件名可下载,tab补,rz出上
Linux文件的上和下,FinalShell文件右键可下文件,先选择root文件夹,然后把他文件往里面拖动,就可以下载了,命令下载,ls -l可以看当前文件目录,sz 文件名可下载,tab补,rz出上