写点什么

Linux 驱动开发 -proc 接口介绍

作者:DS小龙哥
  • 2022 年 4 月 03 日
  • 本文字数:7774 字

    阅读完需:约 26 分钟

1. 前言

Linux 系统上的/proc 目录是一种文件系统,即 proc 文件系统。


与其它常见的文件系统不同的是,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。


当前的实验平台是嵌入式 Linux 开发板,根文件系统挂载成功后,进入命令就能看到proc目录,这个目录里正常情况下已经生成了很多文件。通过cat命令读取这些文件,可以得到很多内核的信息。


比如:查看中断有哪些注册了,中断从上电到现在响应了多少次,杂项设备注册了哪些,帧缓冲节点有哪些,RTC 时间查看,等等。



下面是 proc 目录下文件的功能的详细介绍(资源来源与网络):


2.1、/proc/apm 高级电源管理(APM)版本信息及电池相关状态信息,通常由 apm 命令使用;

2.2、/proc/buddyinfo 用于诊断内存碎片问题的相关信息文件;

2.3、/proc/cmdline 在启动时传递至内核的相关参数信息,这些信息通常由 lilo 或 grub 等启动管理工具进行传递;

2.4、/proc/cpuinfo 处理器的相关信息的文件;

2.5、/proc/crypto 系统上已安装的内核使用的密码算法及每个算法的详细信息列表;

2.6、/proc/devices 系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;

2.7、/proc/diskstats 每块磁盘设备的磁盘 I/O 统计信息列表;(内核 2.5.69 以后的版本支持此功能)

2.8、/proc/dma 每个正在使用且注册的 ISA DMA 通道的信息列表;

2.9、/proc/execdomains 内核当前支持的执行域(每种操作系统独特“个性”)信息列表;

2.10、/proc/fb 帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息;

2.11、/proc/filesystems 当前被内核支持的文件系统类型列表文件,被标示为 nodev 的文件系统表示不需要块设备的支持;通常 mount 一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;

2.12、/proc/interruptsX86 或 X86_64 体系架构系统上每个 IRQ 相关的中断号列表;多路处理器平台上每个 CPU 对于每个 I/O 设备均有自己的中断号;

2.13、/proc/iomem 每个物理设备上的记忆体(RAM 或者 ROM)在系统内存中的映射信息;

2.14、/proc/ioports 当前正在使用且已经注册过的与物理设备进行通讯的输入-输出端口范围信息列表;如下面所示,第一列表示注册的 I/O 端口范围,其后表示相关的设备;

2.15、/proc/kallsyms 模块管理工具用来动态链接或绑定可装载模块的符号定义,由内核输出;(内核 2.5.71 以后的版本支持此功能);通常这个文件中的信息量相当大;

2.16、/proc/kcore 系统使用的物理内存,以 ELF 核心文件(core file)格式存储,其文件大小为已使用的物理内存(RAM)加上 4KB;这个文件用来检查内核数据结构的当前状态,因此,通常由 GBD 通常调试工具使用,但不能使用文件查看命令打开此文件;

2.17、/proc/kmsg 此文件用来保存由内核输出的信息,通常由/sbin/klogd 或/bin/dmsg 等程序使用,不要试图使用查看命令打开此文件;

2.18、/proc/loadavg 保存关于 CPU 和磁盘 I/O 的负载平均值,其前三列分别表示每 1 秒钟、每 5 秒钟及每 15 秒的负载平均值,类似于 uptime 命令输出的相关信息;第四列是由斜线隔开的两个数值,前者表示当前正由内核调度的实体(进程和线程)的数目,后者表示系统当前存活的内核调度实体的数目;第五列表示此文件被查看前最近一个由内核创建的进程的 PID;

2.19、/proc/locks 保存当前由内核锁定的文件的相关信息,包含内核内部的调试数据;每个锁定占据一行,且具有一个惟一的编号;如下输出信息中每行的第二列表示当前锁定使用的锁定类别,POSIX 表示目前较新类型的文件锁,由 lockf 系统调用产生,FLOCK 是传统的 UNIX 文件锁,由 flock 系统调用产生;第三列也通常由两种类型,ADVISORY 表示不允许其他用户锁定此文件,但允许读取,MANDATORY 表示此文件锁定期间不允许其他用户任何形式的访问;

2.20、/proc/mdstat 保存 RAID 相关的多块磁盘的当前状态信息,在没有使用 RAID 机器上,其显示为如下状态:

2.21、/proc/meminfo 系统中关于当前内存的利用状况等的信息,常由 free 命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值;

2.22、/proc/mounts 在内核 2.4.29 版本以前,此文件的内容为系统当前挂载的所有文件系统,在 2.4.19 以后的内核中引进了每个进程使用独立挂载名称空间的方式,此文件则随之变成了指向/proc/self/mounts(每个进程自身挂载名称空间中的所有挂载点列表)文件的符号链接;/proc/self 是一个独特的目录,后文中会对此目录进行介绍;

2.23、/proc/modules 当前装入内核的所有模块名称列表,可以由 lsmod 命令使用,也可以直接查看;如下所示,其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(Live:已经装入;Loading:正在装入;Unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量;

2.24、/proc/partitions 块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目(如下面输出中第三列所示);

2.25、/proc/pci 内核初始化时发现的所有 PCI 设备及其配置信息列表,其配置信息多为某 PCI 设备相关 IRQ 信息,可读性不高,可以用“/sbin/lspci –vb”命令获得较易理解的相关信息;在 2.6 内核以后,此文件已为/proc/bus/pci 目录及其下的文件代替;

2.26、/proc/slabinfo 在内核中频繁使用的对象(如 inode、dentry 等)都有自己的 cache,即 slab pool,而/proc/slabinfo 文件列出了这些对象相关 slap 的信息;详情可以参见内核文档中 slapinfo 的手册页;

2.27、/proc/stat 实时追踪自系统上次启动以来的多种统计信息;如下所示,其中,“cpu”行后的八个值分别表示以 1/100(jiffies)秒为单位的统计值(包括系统运行于用户模式、低优先级用户模式,运系统模式、空闲模式、I/O 等待模式的时间等);“intr”行给出中断的信息,第一个为自系统启动以来,发生的所有的中断的次数;然后每个数对应一个特定的中断自系统启动以来所发生的次数;“ctxt”给出了自系统启动以来 CPU 发生的上下文交换的次数。“btime”给出了从系统启动到现在为止的时间,单位为秒;“processes (total_forks) 自系统启动以来所创建的任务的个数目;“procs_running”:当前运行队列的任务的数目;“procs_blocked”:当前被阻塞的任务的数目;

2.28、/proc/swaps 当前系统上的交换分区及其空间利用信息,如果有多个交换分区的话,则会每个交换分区的信息分别存储于/proc/swap 目录中的单独文件中,而其优先级数字越低,被使用到的可能性越大;下面是作者系统中只有一个交换分区时的输出信息;

2.29、/proc/uptime 系统上次启动以来的运行时间,如下所示,其第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒;

2.30、/proc/version 当前系统运行的内核版本号,在作者的 RHEL5.3 上还会显示系统安装的 gcc 版本,如下所示;

2.31、/proc/vmstat 当前系统虚拟内存的多种统计数据,信息量可能会比较大,这因系统而有所不同,可读性较好;下面为作者机器上输出信息的一个片段;(2.6 以后的内核支持此文件)

2.32、/proc/zoneinfo 内存区域(zone)的详细信息列表,信息量较大

2. 获取 CPU 使用率

下面这份代码是利用/proc/stat文件获取当前 CPU 的占用率详细信息,通过 C 语言代码读取数据后,进行分析,处理。


#include <stdio.h>#include <stdlib.h>#include <unistd.h>
typedef struct cpu_occupy_ //定义一个cpu occupy的结构体{ char name[20]; //定义一个char类型的数组名name有20个元素 unsigned int user; //定义一个无符号的int类型的user unsigned int nice; //定义一个无符号的int类型的nice unsigned int system; //定义一个无符号的int类型的system unsigned int idle; //定义一个无符号的int类型的idle unsigned int iowait; unsigned int irq; unsigned int softirq;}cpu_occupy_t;
double cal_cpuoccupy (cpu_occupy_t *o, cpu_occupy_t *n){ double od, nd; double id, sd; double cpu_use ;
od = (double) (o->user + o->nice + o->system +o->idle+o->softirq+o->iowait+o->irq);//第一次(用户+优先级+系统+空闲)的时间再赋给od nd = (double) (n->user + n->nice + n->system +n->idle+n->softirq+n->iowait+n->irq);//第二次(用户+优先级+系统+空闲)的时间再赋给od
id = (double) (n->idle); //用户第一次和第二次的时间之差再赋给id sd = (double) (o->idle) ; //系统第一次和第二次的时间之差再赋给sd if((nd-od) != 0) cpu_use =100.0 - ((id-sd))/(nd-od)*100.00; //((用户+系统)乖100)除(第一次和第二次的时间差)再赋给g_cpu_used else cpu_use = 0; return cpu_use;}
void get_cpuoccupy (cpu_occupy_t *cpust){ FILE *fd; int n; char buff[256]; cpu_occupy_t *cpu_occupy; cpu_occupy=cpust;
fd = fopen ("/proc/stat", "r"); if(fd == NULL) { perror("fopen:"); exit (0); } fgets (buff, sizeof(buff), fd);
sscanf (buff, "%s %u %u %u %u %u %u %u", cpu_occupy->name, &cpu_occupy->user, &cpu_occupy->nice,&cpu_occupy->system, &cpu_occupy->idle ,&cpu_occupy->iowait,&cpu_occupy->irq,&cpu_occupy->softirq);
fclose(fd);}
double get_sysCpuUsage(){ cpu_occupy_t cpu_stat1; cpu_occupy_t cpu_stat2; double cpu; get_cpuoccupy((cpu_occupy_t *)&cpu_stat1); sleep(1); //第二次获取cpu使用情况 get_cpuoccupy((cpu_occupy_t *)&cpu_stat2);
//计算cpu使用率 cpu = cal_cpuoccupy ((cpu_occupy_t *)&cpu_stat1, (cpu_occupy_t *)&cpu_stat2);
return cpu;}
int main(int argc,char **argv){ while(1) { printf("CPU占用率:%f\n",get_sysCpuUsage()); } return 0;}
复制代码

3. proc 驱动相关接口

Proc 文件接口,主要用于驱动代码调试,获取内核信息,可以直接使用 cat 命令访问 proc 目录下的对应文件接口即可。


需要使用的头文件:


#include <linux/proc_fs.h>#include <linux/fs.h>
复制代码


下面介绍内核里 proc 接口实现的相关函数接口:


1.  在proc目录下创建子目录函数static inline struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent)示例: //注意只能创建单层目录//在proc目录下创建aaa文件夹proc_mkdir("aaa",NULL);
2. 在proc目录下创建文件static inline struct proc_dir_entry *proc_create(const char *name, //文件名称umode_t mode, //模式,默认为0struct proc_dir_entry *parent, //父目录结构const struct file_operations *proc_fops) //文件集合示例://在proc目录下创建一个文件proc_create("aaa/tiny4412_proc_test", 0, NULL, &fops_proc);
3. 删除proc目录下之前创建的文件或者目录void remove_proc_entry(const char *name, //文件的路径struct proc_dir_entry *parent //父目录结构)示例:remove_proc_entry("aaa/tiny4412_proc_test", NULL);注意: 如果是删除目录,需要先把目录下的文件删除掉,每次删除必须保证目录是空的。
复制代码

4. 编写 proc 接口测试驱动

4.1 案例 1

下面驱动代码注册之后,会在 proc 目录下创建一个tiny4412_proc文件,通过 cat 读取这个文件,可以打印驱动代码里设置好的信息。驱动卸载时会删除这个tiny4412_proc文件。


#include <linux/kernel.h>#include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <asm/uaccess.h>#include <asm/io.h>#include <linux/delay.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/proc_fs.h>
static int tiny4412_open(struct inode *inode, struct file *file){ printk("tiny4412_open ok\n"); return 0;}
static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff){ copy_to_user(buf,"123456",6); printk("tiny4412_read调用成功.\n"); return 0;}
static int tiny4412_release(struct inode *inode, struct file *file){ return 0;}
static struct file_operations tiny4412_fops={ .open=tiny4412_open, .read=tiny4412_read, .release=tiny4412_release,};
static int __init tiny4412_init(void){ proc_mkdir("wbyq",0); /*创建内核接口: proc 存放内核信息*/ proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops); printk("驱动安装成功.\n"); return 0;}
static void __exit tiny4412_exit(void){ remove_proc_entry("wbyq/tiny4412_proc", NULL); remove_proc_entry("wbyq", NULL); printk("驱动卸载成功.\n");}
/*驱动的入口:insmod xxx.ko*/module_init(tiny4412_init);/*驱动的出口: rmmod xxx.ko*/module_exit(tiny4412_exit);/*模块的许可证*/MODULE_LICENSE("GPL");/*模块的作者*/MODULE_AUTHOR("wbyq");
复制代码

4.2 案例 2

下面这份代码是在字符设备框架代码里增加了 proc 接口,驱动安装之后,会在 proc 目录下创建tiny4412_proc文件,通过 cat 命令读取tiny4412_proc文件,可以打印出当前主设备号下所有的子设备信息。


#include <linux/kernel.h> #include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/io.h>#include <asm/uaccess.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/device.h>#include <linux/mutex.h>#include <linux/list.h>#include <linux/proc_fs.h>static struct class *tiny4412_beep_class;static unsigned int major=0; //主设备号
static LIST_HEAD(tiny4412_beep_list); //链表头static DEFINE_MUTEX(tiny4412_beep_mtx); //互斥锁
#define DYNAMIC_MINORS 64 /* like dynamic majors */static DECLARE_BITMAP(beep_minors, DYNAMIC_MINORS);
struct tiny4412_beep_device{ int minor; /*次设备号*/ const char *name; /*设备节点的名称*/ const struct file_operations *fops; /*文件操作集合*/ struct list_head list; //链表};
int tiny4412_beep_register(struct tiny4412_beep_device *beep_dev){ struct tiny4412_beep_device *c; dev_t dev; INIT_LIST_HEAD(&beep_dev->list); mutex_lock(&tiny4412_beep_mtx);
//查找传入的次设备号是否冲突 list_for_each_entry(c, &tiny4412_beep_list, list) { if(c->minor == beep_dev->minor) { mutex_unlock(&tiny4412_beep_mtx); return -EBUSY; } } //自动分配 if(beep_dev->minor == MISC_DYNAMIC_MINOR) { int i = find_first_zero_bit(beep_minors,DYNAMIC_MINORS); if (i >= DYNAMIC_MINORS) { mutex_unlock(&tiny4412_beep_mtx); return -EBUSY; } beep_dev->minor = DYNAMIC_MINORS - i - 1; set_bit(i,beep_minors); }
//合成设备号 dev = MKDEV(major, beep_dev->minor);
//创建设备节点 device_create(tiny4412_beep_class,NULL,dev,NULL,"%s", beep_dev->name); list_add(&beep_dev->list,&tiny4412_beep_list); //解锁 mutex_unlock(&tiny4412_beep_mtx); return 0;}
int tiny4412_beep_deregister(struct tiny4412_beep_device *beep_dev){ int i = DYNAMIC_MINORS - beep_dev->minor - 1;
mutex_lock(&tiny4412_beep_mtx); list_del(&beep_dev->list);
//将dev目录下的文件删除掉 device_destroy(tiny4412_beep_class, MKDEV(major, beep_dev->minor)); if (i < DYNAMIC_MINORS && i >= 0) clear_bit(i, beep_minors); mutex_unlock(&tiny4412_beep_mtx); return 0;}
EXPORT_SYMBOL_GPL(tiny4412_beep_register); EXPORT_SYMBOL_GPL(tiny4412_beep_deregister);
//底层open函数static int tiny4412_beep_open(struct inode * inode, struct file * file){ //得到次设备号 int minor = iminor(inode); struct tiny4412_beep_device *c; struct file_operations *new_fops,*old_fops; mutex_lock(&tiny4412_beep_mtx); //遍历链表--找到链表里相同的次设备号 list_for_each_entry(c,&tiny4412_beep_list, list) { if (c->minor == minor) { new_fops = fops_get(c->fops); //得到47次设备号对应的结构体地址 break; } } file->f_op = new_fops; //改变指向--文件操作集合的指向 if(file->f_op->open) { file->f_op->open(inode,file); } fops_put(old_fops); mutex_unlock(&tiny4412_beep_mtx); return 0;}
static const struct file_operations tiny4412_beep_fops ={ .owner = THIS_MODULE, .open = tiny4412_beep_open,};
static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t cnt, loff_t *loff){ struct tiny4412_beep_device *c; //遍历链表--找到链表里相同的次设备号 list_for_each_entry(c,&tiny4412_beep_list, list) { printk("%d %s\n",c->minor,c->name); } return 0;}
static struct file_operations tiny4412_fops={ .read=tiny4412_read,};
static int __init tiny4412_beep_class_init(void){ /*1. 创建设备类*/ tiny4412_beep_class=class_create(THIS_MODULE,"tiny4412_beep"); /*2. 注册字符设备*/ major=register_chrdev(0,"tiny4412_beep",&tiny4412_beep_fops); proc_mkdir("wbyq",0); /*创建内核接口: proc 存放内核信息*/ proc_create("wbyq/tiny4412_proc",0, NULL, &tiny4412_fops); return 0;}
static void __exit tiny4412_beep_class_cleanup(void){ remove_proc_entry("wbyq/tiny4412_proc", NULL); remove_proc_entry("wbyq", NULL); //注销设备类 class_destroy(tiny4412_beep_class); //注销字符设备 unregister_chrdev(major,"tiny4412_beep");}
module_init(tiny4412_beep_class_init);module_exit(tiny4412_beep_class_cleanup);
MODULE_LICENSE("GPL");MODULE_AUTHOR("wbyq");
复制代码


发布于: 刚刚阅读数: 2
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux驱动开发-proc接口介绍_4月月更_DS小龙哥_InfoQ写作平台