IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 分析Linux下的LED驱动例程 -> 正文阅读

[系统运维]分析Linux下的LED驱动例程

Linux下的驱动与无操作系统的驱动的区别:

没有操作系统的时候,驱动程序直接访问相关寄存器的相应的位即可实现目标需求,当有操作系统的时候,我们则需要在驱动程序里面设计面向操作系统内核的接口,且这样的接口由操作系统规定,对一类设备而言结构一致,独立于具体的设备。可见,当系统中存在操作系统的时候,驱动变成了连接硬件和内核的桥梁,并不会直接服务于顶层应用程序,顶层程序需要通过系统调用访问VFS再间接调用驱动程序访问硬件。

那么问题来了,有了操作系统之后,驱动反而变得更复杂,那还需要操作系统做什么。首先操作系统的存在主要是为了高效实现多并发,还有就是虚拟内存管理机制。那么其对于驱动而言,说白了就是以复杂化底层驱动程序为代价便利顶层程序调用,当驱动程序都按照操作系统给出的独立于设备的接口而设计时,那么应用程序就可以使用统一的系统调用接口来访问各种设备。

接下来以Linux下的一个LED驱动为例看看驱动程序干了些什么,因为Linux的驱动框架庞大而复杂,这里只解释一下代码涉及到的部分,并不展开细谈。


#include <linux/modules.h>           
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>

/*自定义的设备结构体*/
struct led_dev
{
    struct cdev cdev;             //字符设备cdev结构体
    char value;                   //led亮时为1,灭时为0,用户可读写此值 
};

struct led_dev *led_devp;         //定义为全局变量,在卸载函数可以直接调用

/*以下这部分信息可以通过命令 modinfo <模块名> 获得*/
MODULE_AUTHOR("YeZhuLaiPi");       //声明作者
MODULE_LICENSE("GPL v2");         //表示遵循GPL协议,没有的话内核会发出警告

/*打开函数*/
int led_open(struct inode *inode, struct file *filp) //inode是节点结构体,描述一个文件,file是文件结构体,描述一个打开的文件
{
    struct led_dev *dev;
    dev = container_of(inode->i_cdev, struct led_dev, cdev); //通过传入的inode找到自定义结构体指针,但实际上并不存在这个结构体
    filp->private_data = dev;     //让自定义的设备结构体作为设备的私有信息,后面会通过这个私有信息拿到该结构体并访问到value值
    return 0;
}

/*关闭函数*/
int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/*读函数和写函数,两者可有可无,因为通过ioctl函数就可以完成数据传输并驱动*/
ssize_t led_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct led_dev *dev = filp->private_data;    //在这里拿到自定义结构体
    if (copy_to_user(buf, &(dev->value), 1))     //写入数据到用户空间
        return -EFAULT;
    return 1;
}

ssize_t led_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct led_dev *dev = filp->private_data;   //同理在这里拿到自定义结构体
    if (copy_from_user(&(dev->value), buf, 1))  //从用户空间拿到数据
        return -EFFAULT;
    if (dev->value == 1)                        //根据写入的值控制灯的亮灭
        led_on();                               //开灯函数,这里省略
    else
        led_off();                              //关灯函数同上
    return 1;
}

/*ioctl函数*/
int led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct led_dev *dev = filp->private_data;   //同上在这里拿到自定义结构体
    switch (cmd)
    {
        case 1:
            dev->value = 1;
            led_on();
        break;
        case 0:
            dev->value = 0;
            led_off();
        break;
        default:                     //不支持的命令
            return -ENOTTY;
    }

}

/*file_operations结构体,通过赋值绑定操作函数*/
struct file_operation led_fops = 
{
    .read = led_read,
    .write = led_write,
    .unlock_ioctl = led_ioctl,
    .open = led_open,
    .release = led_release,
};

/*设置字符设备cdev结构体*/
static void led_setup_cdev(struct led_dev *dev, int devno)
{
    cdev_init(&dev->cdev, &led_fops);           //初始化cdev成员,建立与led_fops之间的连接
    cdev_add(&dev->cdev, devno, 1);             //注册设备,在此之前得有设备号。这里可进行容错处理
}

/*模块加载函数*/
int led_init(void)
{
    int result;
    result = alloc_chrdev_region(&dev, 0, 1, "LED");         //这里用动态方式申请设备号
    if (result < 0)
        return result;
    led_devp = kmalloc(sizeof(struct led_dev), GFP_KERNEL);  //申请动态内存
    led_setup_cdev(led_devp, result);                        
    led_gpio_init();                                         //初始化IO,这里省略
    return 0;
}

/*模块卸载函数*/
void led_cleanup(void)
{
    cdev_del(&led_dev->cdev);                                //删除设备
    kfree(led_devp);                                         //释放内存
    unregister_chrdev_region(result, 1);                     //注销设备号
}

module_init(led_init);                         //绑定加载函数
module_exit(led_exit);                         //绑定卸载函数

补充

可以发现在已经有了全局变量struct led_dev *led_devp的情况下,读写函数依旧要通过函数传进来的的inode和file来找到对应的cdev结构体,所以我猜想读写函数等操作函数无法访问到该程序的全局变量


接下来理一下整个驱动程序的逻辑关系(红色代表自定义函数)

加载函数主要实现:

1.申请设备号 - alloc_chrdev_region()

2.为自定义结构体分配动态内存 - kmalloc()

3.对自定义结构体里的cdev进行初始化 - led_setup_cdev()

4.初始化IO - led_gpio_init()

cdev初始化函数主要实现:

1.初始化cdev - cdev_init()

2.添加一个字符设备 - cdev_add()

卸载函数主要实现:

1.注销字符设备 - cdev_del()

2.释放内存 - kfree()

3.注销设备号 - unregister_chrdev_region()

然后剩下的就是操作函数,还有变量、结构体的定义,程序中省略的部分就是对寄存器的操作,只要知道寄存器对应的虚拟地址,再进行位操作即可

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 22:09:38  更:2021-11-14 22:10:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 23:58:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码