(2.1)file_operation 实现设具体操作:概述与实现

本文详细介绍在Linux环境下,如何为LED设备编写驱动程序。从设备文件操作、GPIO配置,到使用file_operations结构体实现读写功能,再到设备节点的创建与管理,文章提供了完整的实践步骤与代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/* AUTHOR: Pinus

* Creat on : 2018-10-11

* KERNEL : linux-4.4.145

* BOARD : JZ2440(arm9 s3c2440)

* REFS : 韦东山视频教程第二期

《LINUX内核源码情景分析》

*/

概述

       Unix类系统将设备也看作是文件,通过操作文件的方式操作硬件。而操作文件的方式无非就是open、read、write、close等,将这些通用的文件操作函数抽象出来,就是file_operation结构体。(实际内核定义如下,可以看见各种会用到的函数里面都有)

struct file_operations {
    struct module *owner;
    ...
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ...
    int (*open) (struct inode *, struct file *);
    ...

};

      那么显然,要想编写出实际有用的代码,我们起码要实现对应的函数,这样当你在应用程序中调用open,就会实际调用设备驱动的open。其重要性可见一斑。【思考一:内核是如何将app里的操作函数和驱动里的操作函数联系上的呢?】先通过led实验了解如何简单实用,和驱动的简单框架。

 

实验

目标:点亮开发板上的led灯

一、包含头文件,写出入口和出口函数,添加必要协议和修饰

头文件,协议和修饰就略了,要是这不知道就去看前一篇吧。《(1) 设备驱动的最基本框架》

1. 入口函数的实现

unsigned int major;

static int __init leds_drv_init(void) /* 入口函数 */
{
    unsigned int minor = 0;

    gpio_va = ioremap(0x56000000, 0x100000);
    if (!gpio_va) {
        return -EIO;
    }

    major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //注册 告诉内核
    if (major < 0)
    {
        printk("leds_dev can't register major number\n");
        return major;
    }

    leds_class = class_create(THIS_MODULE, "leds_class"); //创建一个类

    leds_dev[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds"); //在类下创建设备节点"leds"
    for (minor = 1; minor < 4; minor++)
    {
        leds_dev[minor] = device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);     //创建设备节点"ledx" 次设备号不同
    }

    printk("leds_dev initialized\n");
    return 0;
}

2.主设备号的概念:linux中使用ls -l 查看某个设备的具体信息时,可以看见主设备号和次设备号,内核通过主设备来选择对应的设备和驱动,次设备号则是纯软件的概念,用于用同一个设备驱动使用几个不同的外设,比如一个led_drv驱动led0,led1,led2... 【思考二;内核怎样创建设备,主设备号具体应用是什么呢?】

/* 注册字符设备
* 参数为主设备号、设备名字、file_operations结构;
* 这样,主设备号就和具体的file_operations结构联系起来了,
* 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
* LED_MAJOR可以设为0,表示由内核自动分配主设备号
*/

major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //注册 告诉内核

内核提供了一个register_chrdev()函数用来注册设备(英文翻译一下register char device),

        第一个参数:0,写0表示让内核自动分配主设备号,并返回给major;

        第二个参数:“leds_dev”, 设备名;

        第三个参数;&jz2440_leds_fops,指向一个真实自定义file_operation结构体

static const struct file_operations jz2440_leds_fops = {
    .owner = THIS_MODULE,
    .open = leds_drv_open,
    .write = leds_drv_write,
};

显然正确的实现这个结构体便是重点。

3. 注册设备类,在类下注册具体设备节点

【思考三:为什么要创建类呢?】

leds_class = class_create(THIS_MODULE, "leds_class"); //创建一个类

【思考四:创建设备节点的具体过程?】

leds_dev[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds"); //创建设备节点"leds"
for (minor = 1; minor < 4; minor++)
{
    leds_dev[minor] = device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);    //创建设备节点"ledx" 次设备号不同
}

从这里也可以看出,主设备号是内核用的,次设备号是纯软的概念,给我们编程用的

4. 操作硬件所需要的具体物理地址映射,将物理地址映射为内核的虚拟地址,ioremap为内核提供的函数
 

static unsigned long gpio_va;

#define GPIO_OFT(x) ((x) - 0x56000000)      /* 具体物理地址有s3c2440芯片手册得来 */
#define GPFCON (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))

gpio_va = ioremap(0x56000000, 0x100000);    /* 参数:物理起始地址,要映射的大小1M */

5.出口函数的实现

static void __exit leds_drv_exit(void)

{

    unsigned int minor;

    iounmap(gpio_va);
    unregister_chrdev(major, "leds_dev"); //卸载设备驱动,注意第二个参数要与注册时保持一致
    for (minor = 0; minor < 4; minor++)

    {
       device_unregister(leds_dev[minor]); //卸载类下的设备
    }
    class_destroy(leds_class);             //卸载类

}

看那些函数,就是调用了在入口函数中调用函数的相反函数,上面注册,下面注销,一一对应

 

二、file_operation结构体内函数的实现

那么长时间终于再次回到了我们的课题,file_operation

static const struct file_operations jz2440_leds_fops = {
    .owner = THIS_MODULE,
    .open = leds_drv_open,
    .write = leds_drv_write,
};

file_operations 可以用来实现很多功能,这里我们只要实现两个,open和write

static int leds_drv_open(struct inode *inode, struct file *file)
{
    /*配置GPF4,5,6*/
    GPFCON &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); //现将对应位清零
    GPFCON |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); // 01 输出
    return 0;
}

函数名自己取,需要传递的参数呢从file_operation结构体复制

有过裸板开发的应该很清楚,想点亮led的无非两步,

        a、配置对应GPIO的配置寄存器,使其为输出模式,

        b、再修改对应的数据寄存器,使其输出,就可以点亮led灯了。

显然这里的leds_drv_open()函数,就是配置寄存器

static ssize_t leds_drv_write(struct file *file, const char *buf, size_t count, loff_t *pos)

{
    unsigned int minor = MINOR(file->f_inode->i_rdev); //MINOR(inode->i_cdev); 得到次设备号
    char val;

    copy_from_user(&val, buf, count); //将数据从用户空间传到内核空间 反之copy_to_user

    switch (minor) /* 看,这就是次设备号的纯软的概念,和它的基本应用 */
    {
        case 0: /* /dev/leds */
        {
            printk("/dev/leds: %d %d\n", minor, val);
            if(val==1)
            {
                GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
            }
            else
            {
                GPFDAT |= (1<<4) | (1<<5) | (1<<6);
            }
            break;
        }

        case 1: /* /dev/led1 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<4);
            }
            else
            {
                GPFDAT |= (1<<4);
            }
            break;
        }
        case 2: /* /dev/led2 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<5);
            }
            else
            {
                GPFDAT |= (1<<5);
            }
            break;
        }
        case 3: /* /dev/led3 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<6);
            }
            else
            {
                GPFDAT |= (1<<6);
            }
            break;

        }
        default: printk("/dev/leds: %d %d\ ?? n", minor, val);
    }

    return 0;
}

这样我们就实现了具体的file_operation结构体的具体功能函数,

major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //在这里传入,使之与具体的设备关联在一起

三、测试程序的实现

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

void print_usage(char *drv);

int main(int argc, char **argv)
{
    int fd;
    char* filename;
    char val = 1;

    if (argc != 3)
    {
        print_usage(argv[0]);
        return 1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
        printf("can't open dev nod !\n");

    if (strcmp(argv[2], "on") == 0)
    {
        val = 1;
    }
    else if (strcmp(argv[2], "off") == 0)
    {
        val = 0;
    }
    else
    {
        print_usage(argv[0]);
        return 1;
    }

    printf("%s : %d\r\n", argv[1], val);
    write(fd, &val, 1);

    return 0;
}

/* 打印用法 */ 
void print_usage(char *drv)
{
    printf("Usage : \n");
    printf("%s <dev> <on|off>\n", drv);
    printf("eg. \n");
    printf("%s /dev/leds on\n", drv);
    printf("%s /dev/leds off\n", drv);
    printf("%s /dev/led1 on\n", drv);
    printf("%s /dev/led1 off\n", drv);
    // <>表示内部的参数不可省略,|表示或 ?不把argv[0]打印出来避免一个越界错误
}

编译之后也放在板子上,安装模块,就可以测试了

文中提到的思考题就在下一篇在解答吧,本人也是初学,如有不足请大佬指正

 

具体程序可以通过下面下载实现代码:

https://download.youkuaiyun.com/download/qq_38960810/10734664

 

### LlamaIndex 多模态 RAG 实现 LlamaIndex 支持多种数据类型的接入与处理,这使得它成为构建多模态检索增强生成(RAG)系统的理想选择[^1]。为了实现这一目标,LlamaIndex 结合了不同种类的数据连接器、索引机制以及强大的查询引擎。 #### 数据连接器支持多样化输入源 对于多模态数据的支持始于数据收集阶段。LlamaIndex 的数据连接器可以从多个异构资源中提取信息,包括但不限于APIs、PDF文档、SQL数据库等。这意味着无论是文本还是多媒体文件中的内容都可以被纳入到后续的分析流程之中。 #### 统一化的中间表示形式 一旦获取到了原始资料之后,下一步就是创建统一而高效的内部表达方式——即所谓的“中间表示”。这种转换不仅简化了下游任务的操作难度,同时也提高了整个系统的性能表现。尤其当面对复杂场景下的混合型数据集时,良好的设计尤为关键。 #### 查询引擎助力跨媒体理解能力 借助于内置的强大搜索引擎组件,用户可以通过自然语言提问的形式轻松获得所需答案;而对于更复杂的交互需求,则提供了专门定制版聊天机器人服务作为补充选项之一。更重要的是,在这里实现了真正的语义级关联匹配逻辑,从而让计算机具备了一定程度上的‘认知’功能去理解和回应人类意图背后所蕴含的意义所在。 #### 应用实例展示 考虑到实际应用场景的需求多样性,下面给出一段Python代码示例来说明如何利用LlamaIndex搭建一个多模态RAG系统: ```python from llama_index import GPTSimpleVectorIndex, SimpleDirectoryReader, LLMPredictor, PromptHelper, ServiceContext from langchain.llms.base import BaseLLM import os def create_multi_modal_rag_system(): documents = SimpleDirectoryReader(input_dir='./data').load_data() llm_predictor = LLMPredictor(llm=BaseLLM()) # 假设已经定义好了具体的大型预训练模型 service_context = ServiceContext.from_defaults( chunk_size_limit=None, prompt_helper=PromptHelper(max_input_size=-1), llm_predictor=llm_predictor ) index = GPTSimpleVectorIndex(documents, service_context=service_context) query_engine = index.as_query_engine(similarity_top_k=2) response = query_engine.query("请描述一下图片里的人物表情特征") print(response) ``` 此段脚本展示了从加载本地目录下各类格式文件开始直到最终完成一次基于相似度排序后的top-k条目返回全过程。值得注意的是,“query”方法接收字符串参数代表使用者想要询问的内容,而在后台则会自动调用相应的解析模块并结合先前准备好的知识库来进行推理计算得出结论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值