0

0

Linux系统中的内核交互文件系统:自构proc详解

WBOY

WBOY

发布时间:2024-02-13 23:00:23

|

644人浏览过

|

来源于良许Linux教程网

转载

proc是linux系统中一种特殊的文件系统,它用来提供内核和用户空间的交互接口,如显示内核信息,修改内核参数,控制内核功能等。proc的优点是简单易用,不需要额外的设备或驱动。proc的实现涉及到proc_dir_entry结构体,proc_create函数,seq_file机制等概念。在本文中,我们将介绍linux内核调试技术之自构proc的原理和方法,包括创建和删除proc文件,读取和写入proc文件,使用seq_file机制等,并举例说明它们的使用方法和注意事项。

1、简介

在内核中使用printk可以讲调试信息保存在log_buf缓冲区中,可以使用命令 #cat /proc/kmsg 将缓冲区的数区的数数据打印出来,今天我们就来研究一下,自己写kmsg这个文件,我们取名叫做 mymsg。

2、查看内核中 /proc/kmsg怎么写的!

在Proc_misc.c (fs\proc) 文件中:

void __init proc_misc_init(void)
{
    .........................
        struct proc_dir_entry *entry;
        //这里创建了一个proc入口kmsg
        entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);
        if (entry)
       /*构造一个proc_fops结构*/
       entry->proc_fops = &proc_kmsg_operations;
  ......................... 
}

在Kmsg.c (fs\proc) 文件中:

const struct file_operations proc_kmsg_operations = {
    .read        = kmsg_read,
    .poll        = kmsg_poll,
    .open        = kmsg_open,
    .release    = kmsg_release,
};

在用户空间中使用 cat /proc/kmsg的时候,会调用kmsg_open,在调用kmsg_read函数,读取log_buf中的数据,拷贝到用户空间显示。

3、在写之前,我们需要来学习一下循环队列

环形队列是在实际编程极为有用的数据结构,它有如下特点。

它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单,能很快知道队列是否满为空。能以很快速度的来存取数据。

因为有简单高效的原因,甚至在硬件都实现了环形队列。

环形队列广泛用于网络数据收发,和不同程序间数据交换(比如内核与应用程序大量交换数据,从硬件接收大量数据)均使用了环形队列。

3.1.环形队列实现原理

内存上没有环形的结构,因此环形队列实上是数组的线性空间来实现。那当数据到了尾部如何处理呢?它将转回到0位置来处理。这个的转回是通过取模操作来执行的。

XunpoCMS多国语言企业网站内容管理系统_生成静态页110719
XunpoCMS多国语言企业网站内容管理系统_生成静态页110719

Xunpo CMS多国语言企业网站内容管理系统全功能免费,多国语言(理论上可以做无限语言),可以生成全静态页,产品详页可以自定义文件名,支持产品销售,支持图集、文件下载、在线订单、在线留言等,具有良好的SEO架构,能帮助企业网站在百度、谷歌中获得很好的排名。 寻破多国语言企业网站内容管理系统特性 1. 专为企业设计的网站系统,方便产品展示推广,支持客户在线下单,在线留言。 2. asp.net

下载
因此环列队列的是逻辑上将数组元素q[0]与q[MAXN-1]连接,形成一个存放队列的环形空间。

为了方便读写,还要用数组下标来指明队列的读写位置。head/tail.其中head指向可以读的位置,tail指向可以写的位置。
Linux系统中的内核交互文件系统:自构proc详解

环形队列的关键是判断队列为空,还是为满。当tail追上head时,队列为满时,当head追上tail时,队列为空。但如何知道谁追上谁。还需要一些辅助的手段来判断.

如何判断环形队列为空,为满有两种判断方法。

一.是附加一个标志位tag

当head赶上tail,队列空,则令tag=0,
当tail赶上head,队列满,则令tag=1,

二.限制tail赶上head,即队尾结点与队首结点之间至少留有一个元素的空间。

队列空: head==tail
队列满: (tail+1)% MAXN ==head

Linux系统中的内核交互文件系统:自构proc详解

4、程序编写

#include 
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#include

\#define MYLOG_BUF_LEN 1024
static char mylog_buf[MYLOG_BUF_LEN];
static char tmp_buf[MYLOG_BUF_LEN];
static int mylog_r = 0;
static int mylog_w = 0;
static int mylog_r_tmp = 0;

/*休眠队列初始化*/
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);

/*
*判断环形队列是否为空
*返回0:表示不空 返回1:表示空
*/
static int is_mylog_empty(void)
{
  return (mylog_r == mylog_w);
}

/*
*判断环形队列是否满
*返回0:表示不满 返回1:表示满
*/
static int is_mylog_full(void)
{
  return((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}

/*
*在读取的时候,判断环形队列中数据是否为空
*返回0:表示不空 返回1:表示空
*/
static int is_mylog_empty_for_read(void)
{
  return (mylog_r_tmp == mylog_w);
}

/*
*往循环队列中存字符
*输入:c字符 单位:1byte
*输出:无
*/
static void mylog_putc(char c)
{

  if(is_mylog_full())
  {
    /*如果检测到队列已经满了,则丢弃该数据*/
    mylog_r= (mylog_r + 1) % MYLOG_BUF_LEN;
    
    /*mylog_r_tmp不能大于mylog_r*/
    if((mylog_r_tmp + 1)% MYLOG_BUF_LEN == mylog_r)
      mylog_r_tmp= mylog_r;
    
  }
  mylog_buf[mylog_w]= c;
  /*当mylog_w=1023的时候 (mylog_w+1) % MYLOG_BUF_LEN =0,回到队列头,实现循环*/
  mylog_w= (mylog_w + 1) % MYLOG_BUF_LEN;
  /* 唤醒等待数据的进程*/  
  wake_up_interruptible(&mymsg_waitq); 
}

/*
*从循环队列中读字符
*输入:*p 单位:1byte
*输出:1表示成功
*/
static int mylog_getc(char *p)
{
  /*判断数据是否为空*/
  if (is_mylog_empty_for_read())
  {
    return 0;
  }
  *p = mylog_buf[mylog_r_tmp ];
  mylog_r_tmp = (mylog_r_tmp + 1) % MYLOG_BUF_LEN;
  return 1;
}

/*
*调用myprintk,和printf用法相同
*/
int myprintk(const char *fmt, ...)
{
  va_list args;
  int i;
  int j;

  va_start(args, fmt);
  i= vsnprintf(tmp_buf, INT_MAX, fmt, args);
  va_end(args);
  
  for (j = 0; j return i;
}


static ssize_t mymsg_read(struct file *file, char __user *buf,
      size_t count, loff_t*ppos)
{
  int error=0;
  size_t i=0;
  char c;
  /* 把mylog_buf的数据copy_to_user, return*/

  /*非阻塞 和 缓冲区为空的时候返回*/
  if ((file->f_flags & O_NONBLOCK) && is_mylog_empty())
    return -EAGAIN;
  
  /*休眠队列wait_event_interruptible(xxx,0)-->休眠*/
  error= wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read());
  
  /* copy_to_user*/
  while (!error && (mylog_getc(&c)) && i if (!error)
    error= i;
  /*返回实际读到的个数*/
  return error;
}

static int mymsg_open(struct inode * inode, struct file * file)
{
  mylog_r_tmp= mylog_r;
  return 0;
}


const struct file_operations proc_mymsg_operations = {
  .read= mymsg_read,
  .open= mymsg_open,
  };
static int mymsg_init(void)
{
  struct proc_dir_entry *myentry; kmsg
  myentry= create_proc_entry("mymsg", S_IRUSR, &proc_root);
  if (myentry)
    myentry->proc_fops = &proc_mymsg_operations;
  return 0;
}

static void mymsg_exit(void)
{
  remove_proc_entry("mymsg", &proc_root);
}

module_init(mymsg_init);
module_exit(mymsg_exit);

/*声名到内核空间*/
EXPORT_SYMBOL(myprintk);

MODULE_LICENSE("GPL");

5、测试程序

注意:在上面程序中 使用了 EXPORT_SYMBOL(myprintk);意思是把myprintk可以在整个内核空间使用。

使用方法:①extern int myprintk(const char *fmt, ...);声明

      ② myprintk("first_drv_open : %d\n", ++cnt);使用

\#include 
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#include
\#include

static struct class *firstdrv_class;
static struct class_device  *firstdrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

extern int myprintk(const char *fmt, ...);

static int first_drv_open(struct inode *inode, struct file *file)
{
  static int cnt = 0;
  myprintk("first_drv_open : %d\n", ++cnt);
  /* 配置GPF4,5,6为输出*/
  *gpfcon &= ~((0x3return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf,
size_t count, loff_t * ppos)
{
  int val;
  static int cnt = 0;

  myprintk("first_drv_write : %d\n", ++cnt);

  copy_from_user(&val, buf, count); //  copy_to_user();

  if (val == 1)
  {
    // 点灯
    *gpfdat &= ~((1else
  {
    // 灭灯
    *gpfdat |= (1return 0;
}

static struct file_operations first_drv_fops = {
  .owner = THIS_MODULE,  /* 这是一个宏,推向编译模块时自动创建的__this_module变量*/
  .open = first_drv_open,  
  .write  =  first_drv_write,   
};


int major;
static int first_drv_init(void)
{
  myprintk("first_drv_init\n");
  major= register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核

  firstdrv_class= class_create(THIS_MODULE, "firstdrv");

  firstdrv_class_dev= class_device_create(firstdrv_class, NULL, MKDEV(major, 0),
 NULL, "xyz"); /* /dev/xyz*/

  gpfcon= (volatile unsigned long *)ioremap(0x56000050, 16);
  gpfdat= gpfcon + 1;

  return 0;
}

static void first_drv_exit(void)
{
  unregister_chrdev(major,"first_drv"); // 卸载

  class_device_unregister(firstdrv_class_dev);
  class_destroy(firstdrv_class);
  iounmap(gpfcon);
}

module_init(first_drv_init);
module_exit(first_drv_exit);


MODULE_LICENSE("GPL");

6、在tty中测试效果

# insmod my_msg.ko``# insmod first_drv.ko``# cat /proc/mymsg``mymsg_open mylog_r_
tmp=0``first_drv_init

通过本文,我们了解了Linux内核调试技术之自构proc的原理和方法,它们可以用来实现对内核的调试和控制。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如使用正确的文件名,使用正确的读写函数,使用正确的seq_file操作等。proc是Linux系统中一种有用而灵活的文件系统,它可以实现对内核的交互和反馈,也可以提升内核的可维护性和可扩展性。希望本文能够对你有所帮助和启发。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

533

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

12

2026.01.06

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1010

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

59

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

359

2025.12.29

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

25

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 6.9万人学习

Git 教程
Git 教程

共21课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号