51 linux设备驱动的proc调用接口

本文介绍Linux系统中Proc文件系统的使用方法及其实现原理。通过创建Proc目录和文件,可以为简单功能的硬件设备提供读写接口。文章详细解释了Proc目录入口结构、文件操作函数以及相关API,并给出具体示例。

前面实现的设备驱动提供给用户调用的接口基本都是基于字符设备接口, 用户需要应用程序来调用设备驱动.
但一些比较简单功能的设备驱动,如只需读写功能,可以使另一种用户接口.

在系统下的proc目录, 有些文件是可读写的,而且可通过读写改变系统的配置,也可以改变设备驱动里的设置.
如改变printk的输出级别”echo “11 8” > /proc/sys/kernel/printk”

如硬件功能比较简单,设备驱动也可以实现这种接口.如led灯,蜂鸣器, 可实现通过文件写0或写1来控制设备的开关.

"include/linux/proc_fs.h"

//在linux系统下, proc目录里的每个子目录或子目录里的文件都对应着一个proc_dir_entry的对象
struct proc_dir_entry {
    ...
    const struct file_operations *proc_fops; //如硬件功能比较多,也可以实现像字符设备的接口
        ...
    void *data; //用于read_proc, write_proc函数里的data参数
    read_proc_t *read_proc; // 读proc文件时触发
    write_proc_t *write_proc;  //写proc文件时触发,read_proc和write_proc就是实现只有读写功能的proc文件接口
    ...
};

typedef int (read_proc_t)(char *page, char **start, off_t off,
              int count, int *eof, void *data); //page指针指向用户接收缓冲区的地址, count表示用户缓冲区的大小, data就是由创建proc_dir_entry对象时指定的参数

typedef int (write_proc_t)(struct file *file, const char __user *buffer,
               unsigned long count, void *data); //buffer指针指向用户数据缓冲区地址, count为数据缓冲区大小
///////////
extern struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent); //创建proc目录下的子目录, parent为在proc目录的一个子目录里创建. 如parent为NULL,则创建出来的子目录是在/proc目录下的. 并返回创建出来的目录的proc_dir_entry对象的地址.


extern struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode,
                        struct proc_dir_entry *parent); //在parent指向目录下创建文件,此文件可读写.
            //注意如需读写功能,还需初始化read_proc和write_proc成员

struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
                struct proc_dir_entry *parent,
                const struct file_operations *proc_fops,
                void *data); //在parent指向的目录下创建文件,此文件支持像字符设备一样的操作

extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent);//把proc下的子目录或文件移除. 如是文件则需通过parent指定它是在哪个目录下的

/////////////////////////////////
测试代码:

test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>

struct proc_dir_entry *dir;
struct proc_dir_entry *myfile;

int myread(char *page, char **start, off_t off,
              int count, int *eof, void *data)
{
    static int num = 0;
    sprintf(page, "proc read: %d, %x\n", num++, data);

    return  strlen(page);
}

int mywrite(struct file *file, const char __user *buffer,
               unsigned long count, void *data)
{
    char *p = (char *)buffer;   
    p[count] = 0;
    printk("proc write: %s, %x\n", p, data);
    return count;
}

static int __init test_init(void)
{
    dir = proc_mkdir("mydir", NULL);
    myfile = create_proc_entry("myfile", 0644, dir);

    myfile->data = 0x11; //测试data, 在myread, mywrite函数里获取
    myfile->read_proc = myread;
    myfile->write_proc = mywrite; 

    return 0;
}

static void __exit test_exit(void)
{
    remove_proc_entry("myfile", dir);
    remove_proc_entry("mydir", NULL);
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");

加载模块后,对”/proc/mydir/myfile”文件用cat和echo读写即可

<think>我们讨论的上下文已经从Linux内核转向了51单片机(通常指Intel 8051或其兼容微控制器)。在51单片机中,没有Linux内核中的`proc`文件系统概念,因此我们需要重新定义问题中的术语。 在51单片机编程中: 1. **ISR(中断服务程序)**:与Linux内核中的概念类似,是响应硬件中断而执行的函数。 2. **proc函数**:这个术语在51单片机中没有标准定义。根据问题上下文,可能是指**普通的过程函数**(即非中断服务程序的主循环函数或子程序)。 因此,我们重新解释问题: - **ISR**:中断服务程序,响应硬件中断事件。 - **proc函数**:理解为普通的过程函数(主循环中的函数或子程序)。 --- ### **51单片机中ISR与普通函数的区别及运行时机** #### **1. 中断服务程序(ISR)** - **触发条件**:由硬件中断事件触发(如定时器溢出、外部引脚电平变化、串口接收完成等)。 - **运行时机**: - 当硬件中断发生时,CPU暂停当前正在执行的代码。 - CPU跳转到预定义的中断向量地址(如外部中断0的向量地址是`0x0003`)。 - 执行用户编写的ISR函数。 - **关键特性**: - **执行时间短**:为避免影响其他中断和主程序,ISR应尽可能短小。 - **不可重入**:51单片机默认不支持中断嵌套(除非手动开启),因此ISR执行时不会被其他中断打断。 - **寄存器保护**:ISR进入时需手动保存相关寄存器(编译器通常自动处理部分工作)。 - **示例代码**(使用Keil C51语法): ```c #include <reg51.h> // 外部中断0的ISR void EX0_ISR(void) interrupt 0 // 中断号0对应外部中断0 { P1 ^= 0x01; // 翻转P1.0引脚电平 } ``` #### **2. 普通过程函数(proc函数)** - **触发条件**:由主程序或子程序调用。 - **运行时机**: - 在`main()`函数的无限循环中按顺序执行。 - 被其他函数调用时执行。 - **关键特性**: - **可长时间运行**:无严格时间限制(但需考虑整体系统响应)。 - **可调用其他函数**:支持多层嵌套调用。 - **示例代码**: ```c void delay_ms(unsigned int ms) { unsigned int i, j; for (i=0; i<ms; i++) for (j=0; j<114; j++); // 粗略延时1ms } void main() { while (1) { P2 = 0xFF; // 点亮所有LED(假设低电平驱动) delay_ms(500); // 延时500ms P2 = 0x00; // 熄灭所有LED delay_ms(500); } } ``` --- ### **执行流程对比** ```mermaid graph TB A[上电启动] --> B[main函数初始化] B --> C{主循环} C --> D[执行过程函数1] D --> E[执行过程函数2] E --> C subgraph 中断触发 F[硬件中断] --> G[跳转到ISR] G --> H[执行ISR] H --> I[返回主程序断点] end C -.-> F // 中断可打断主循环 ``` --- ### **关键交互约束** 1. **ISR与主程序共享全局变量**: - 若主程序和ISR同时修改同一全局变量,需通过**关中断**或**原子操作**保护数据。 - 示例: ```c volatile unsigned int counter; // 声明为volatile防止编译器优化 void timer_isr() interrupt 1 { counter++; // ISR修改计数器 } void main() { while (1) { if (counter >= 1000) { counter = 0; // 主程序重置计数器 // 注意:此处可能被中断打断,需关中断保护! } } } ``` 2. **ISR中避免调用阻塞函数**: - 例如:避免在ISR中调用`delay_ms()`,否则会导致系统响应延迟。 --- ### **常见问题解答** #### Q: 为什么我的51单片机程序跑飞了? - **可能原因**: - ISR执行时间过长,导致主程序无法及时处理关键任务。 - ISR或主程序修改了共享变量未加保护(如`counter++`非原子操作)。 #### Q: 如何实现中断嵌套? - 51单片机默认不开启中断嵌套。需在ISR中手动打开中断允许位(EA): ```c void UART_ISR() interrupt 4 { EA = 0; // 关中断(可选,保护关键段) // 处理UART中断 EA = 1; // 开中断(允许嵌套) } ``` --- §§相关问题§§ 1. 51单片机中断优先级如何设置? 2. 如何保护ISR与主程序共享的全局变量? 3. 51单片机的中断响应延迟由哪些因素决定? 4. 为什么在ISR中避免使用浮点运算? 5. 如何通过中断实现按键防抖?</think>在51单片机(Intel 8051架构)中,**proc函数**和**ISR函数**是两种不同执行上下文的代码单元,它们的关系和运行时机如下: --- ### **1. 核心区别** | **特性** | **proc函数**(主循环/普通函数) | **ISR函数**(中断服务程序) | |------------------|----------------------------------------|----------------------------------------| | **执行上下文** | 主程序循环(非中断上下文) | 中断上下文(硬件触发) | | **触发方式** | 由主程序`main()`顺序调用 | 硬件中断信号(如定时器、外部引脚等) | | **运行时机** | CPU空闲时顺序执行 | **立即抢占**主程序,最高优先级执行 | | **执行限制** | 可执行复杂逻辑/延时操作 | 必须短小精悍,避免阻塞 | | **典型应用** | 数据处理、状态机、外设初始化 | 实时响应(按键检测、定时器事件等) | --- ### **2. 运行时机详解** #### **(1) proc函数的运行** - **触发条件**:在`main()`函数的无限循环中调用: ```c void main() { init(); // 初始化 while(1) { // 主循环 proc_task1(); // 普通函数1 proc_task2(); // 普通函数2 } } ``` - **特点**: - 按代码顺序执行 - 可包含延时(如`delay_ms(100)`) - 若执行时间过长,会延迟中断响应 #### **(2) ISR函数的运行** - **触发条件**:硬件中断事件(例如定时器溢出): ```c void Timer0_ISR() interrupt 1 { // 中断号1对应定时器0 TF0 = 0; // 清除中断标志 // 快速处理逻辑 } ``` - **特点**: - **立即抢占**:中断发生时,CPU暂停当前主循环,跳转到ISR - **原子性操作**:执行期间不会被其他中断打断(除非启用中断嵌套) - **严格时限**:通常在微秒级完成,避免影响系统实时性 --- ### **3. 关键交互关系** ```mermaid graph LR A[主循环 proc_task1] --> B[proc_task2] C[硬件中断] --> D{中断触发?} D -->|是| E[跳转至ISR] E --> F[执行ISR] F --> G[返回主循环断点] D -->|否| B ``` - **ISR可打断proc**:中断发生时,CPU保存当前状态,执行ISR后恢复主程序 - **proc不可打断ISR**:ISR执行期间主循环暂停 - **数据共享**:通过`volatile`全局变量通信(需关中断保护临界区) ```c volatile bit flag = 0; // ISR与proc共享标志 void UART_ISR() interrupt 4 { flag = 1; // 设置标志 } void main() { while(1) { if(flag) { // proc检测标志 EA = 0; // 关中断(进入临界区) process_data(); // 安全处理数据 flag = 0; // 清除标志 EA = 1; // 开中断 } } } ``` --- ### **4. 设计原则** 1. **ISR最小化**: - 仅做**关键状态记录**(如置位标志) - 复杂操作交给proc处理(通过标志触发) - 避免在ISR中调用函数或使用浮点运算 2. **proc实时性优化**: - 主循环中函数尽量短小 - 通过**状态机**拆分长任务 - 示例: ```c void proc_state_machine() { static char state = 0; switch(state) { case 0: step1(); state++; break; // 分步执行 case 1: step2(); state++; break; } } ``` --- ### **5. 中断响应流程** 当定时器中断发生时: 1. 完成当前指令 2. 将PC(程序计数器)压栈 3. 跳转到中断向量地址(如定时器0:`0x000B`) 4. 执行`Timer0_ISR()` 5. 执行`RETI`指令恢复现场 6. 继续执行主循环 > ⚠️ **中断延迟** = 当前指令执行时间 + ISR响应时间(通常3~8个机器周期) ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值