`

使用/proc实现内核与用户空间通信

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
1. 前言
 
Linux内核空间与用户空间的通信可通过"/proc"目录的文件读写来实现,如果只是控制内核中的参数而不是传输较多数据的话,用“/proc”是很合适的。另外一种内核与用户空间通信方式方式是使用内核设备的读写或IOCTL来实现,以后再介绍。
 
2. /proc概述
 
/proc目录是系统模拟出来的一个文件系统,本身并不存在于磁盘上,其中的文件都表示内核参数的信息,这些信息分两类,一类是可都可写的,这类参数都在“/proc/sys”目录下,另一类是只读的,就是“/proc/sys”目录之外的其他目录和文件,当然这只是一种惯例,实际在其他目录下建立可读写的/proc文件也是可以的。
 
操作/proc目录不需要特殊工具,在用户层看来就是一普通文件,在shell中用“cat”命令进行查看,用“echo”命令来写文件信息。
 
Linux内核在2.4以后/proc目录文件的建立已经变得很容易,以前版本都还需要构造文件操作结构来实现,现在只需要调用简单函数就可以了。/proc文件通过是create_proc_entry()函数来建立,使用remove_proc_entry()函数来删除,建立新目录可以通过proc_mkdir()函数调用,这些函数在fs/proc/generic.c中定义,通常我们不必直接使用 create_proc_entry()函数来建立,而是通过这个函数的包裹函数来实现。
 
3. 只读/proc文件
 
内核编译选项要设置CONFIG_PROC_FS。
 
3.1 建立/proc只读项
 
只读的/proc文件可以通过create_proc_read_entry()或create_proc_info_entry()函数来建立,在模块初始化时调用,:
/* include/linux/proc_fs.h */

static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
 mode_t mode, struct proc_dir_entry *base,
 read_proc_t *read_proc, void * data)
{
 struct proc_dir_entry *res=create_proc_entry(name,mode,base);
 if (res) {
  res->read_proc=read_proc;
  res->data=data;
 }
 return res;
}
 
该函数需要5个参数:
name:要建立的文件名
mode:文件模式
base:所在的目录
read_proc:这是个函数指针,表示读取文件内容的函数
data:传递给read_proc函数的用户参数指针
 
static inline struct proc_dir_entry *create_proc_info_entry(const char *name,
 mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)
{
 struct proc_dir_entry *res=create_proc_entry(name,mode,base);
 if (res) res->get_info=get_info;
 return res;
}

该函数需要4个参数:
name:要建立的文件名
mode:文件模式
base:所在的目录
get_info:这是个函数指针,表示读取文件内容的函数,这个函数比上面的read_proc函数少一个用户输入参数
 
对于base,内核已经预定义了一些目录/proc/net, /procbus, /proc/fs, /proc/driver, 这些是在fs/proc/root.c中定义的:
 
struct proc_dir_entry *proc_net, *proc_bus, *proc_root_fs, *proc_root_driver;
 
3.2 删除/proc只读项
 
只读的/proc文件可以通过remove_proc_entry()函数来建立,在模块删除时调用,该函数在fs/proc/generic.c中定义:
 
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
该函数需要2个参数:
name:要建立的文件名
parent:父目录
 
3.3 网络相关/proc的建立和删除
 
对于网络参数(/proc/net),内核更提供了proc_net_create()和proc_net_remove()包裹函数来建立和删除/proc/net文件:
 
static inline struct proc_dir_entry *proc_net_create(const char *name,
 mode_t mode, get_info_t *get_info)
{
 return create_proc_info_entry(name,mode,proc_net,get_info);
}
static inline void proc_net_remove(const char *name)
{
 remove_proc_entry(name,proc_net);
}
 
proc_net就是已经预定义好的"/proc/net"目录的指针,这样在建立网络部分的只读文件时就直接调用这两个函数就可以了。
 
3.4 举例
net/ipv4/af_inet.c:
...
// 建立/proc/net/netstat文件
 proc_net_create ("netstat", 0, netstat_get_info);
...
 
netstat_get_info()函数在net/ipv4/proc.c文件中定义,函数的参数格式是固定的:
//buffer是数据输出的缓冲区,要输出的数据都写到这个缓冲区;
//start用来返回buffer中起始数据的位置;
//offset指定偏移start所指数据相对buffer起点的偏移,实际start是通过buffer和
//offset计算出来的;
//length表示buffer的长度,是由内核自己分配的,编程时要检查向缓冲区写的数据长度
//是否超过length规定的限值。
 
int netstat_get_info(char *buffer, char **start, off_t offset, int length)
{
 int len, i;
// len记录写入缓冲区的数据长度,所有数据长度都要累加
 len = sprintf(buffer,
        "TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed"
        " EmbryonicRsts PruneCalled RcvPruned OfoPruned"
        " OutOfWindowIcmps LockDroppedIcmps ArpFilter"
        " TW TWRecycled TWKilled"
        " PAWSPassive PAWSActive PAWSEstab"
        " DelayedACKs DelayedACKLocked DelayedACKLost"
        " ListenOverflows ListenDrops"
        " TCPPrequeued TCPDirectCopyFromBacklog"
        " TCPDirectCopyFromPrequeue TCPPrequeueDropped"
        " TCPHPHits TCPHPHitsToUser"
        " TCPPureAcks TCPHPAcks"
        " TCPRenoRecovery TCPSackRecovery"
        " TCPSACKReneging"
        " TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder"
        " TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo"
        " TCPLoss TCPLostRetransmit"
        " TCPRenoFailures TCPSackFailures TCPLossFailures"
        " TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans"
        " TCPTimeouts"
        " TCPRenoRecoveryFail TCPSackRecoveryFail"
        " TCPSchedulerFailed TCPRcvCollapsed"
        " TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv"
        " TCPAbortOnSyn TCPAbortOnData TCPAbortOnClose"
        " TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger"
        " TCPAbortFailed TCPMemoryPressures\n"
        "TcpExt:");
 for (i=0; i<offsetof(struct linux_mib, __pad)/sizeof(unsigned long); i++)
  len += sprintf(buffer+len, " %lu", fold_field((unsigned long*)net_statistics, sizeof(struct linux_mib), i));
 len += sprintf (buffer + len, "\n");
 if (offset >= len)
 {
  *start = buffer;
  return 0;
 }
//计算数据起始指针
 *start = buffer + offset;
 len -= offset;
// 检查写入的长度是否溢出
 if (len > length)
  len = length;
 if (len < 0)
  len = 0;
 return len;
}
 
4. 可读写的/proc文件
 
要支持可读写的/proc,内核编译选项要设置CONFIG_SYSCTL。
 
可读写/proc文件按惯例通常放在/proc/sys目录下,这些文件对应的内核参数或者是全局变量,或者是动态分配的内存空间,不能是临时变量。
 
4.1 建立函数
 
建立可读写的/proc文件使用register_sysctl_table()函数来登记,该函数在kernel/sysctl.c中定义,声明如下:
 
struct ctl_table_header *register_sysctl_table(ctl_table * table,  int insert_at_head);
 
该函数返回一个struct ctl_table_header结构的指针,在释放时使用;
该函数第一个参数table是sysctl控制表,定义如下:
 
/* include/linux/sysctl.h */
typedef struct ctl_table ctl_table;
struct ctl_table
{
 int ctl_name;  /* 数值表示的该项的ID */
 const char *procname; /* 名称 */
 void *data;             /* 对于的内核参数 */
 int maxlen;             /* 该参数所占的存储空间 */
 mode_t mode;            /* 权限模式:rwxrwxrwx */
 ctl_table *child;       /* 子目录表 */
 proc_handler *proc_handler; /* 读写数据处理的回调函数 */
 ctl_handler *strategy;  /* 读/写时的回调函数,是对数据的预处理,
     该函数是在读或写操作之前执行,该函数返回值
     <0表示出错;==0表示正确,继续读或写;>0表
     示读/写操作已经在函数中完成,可以直接返回了*/
 struct proc_dir_entry *de; /* /proc控制块指针 */
 void *extra1;  /* 额外参数,常在设置数据范围时用来表示最大最小值 */
 void *extra2;
};
注意该结构中的第6个参数子目录表,这使得该表成为树型结构。
 
第二个参数表示链表的插入方式,是插入到链表头还是链表尾;

由此可知重要的是struct ctl_table结构的填写,而最重要的是结构项proc_handler,该函数处理数据的输入和输出,如果不是目录而是文件,该项是不可或缺的。早期内核版本中这些都需要单独编写,现在2.4以后内核提供了一些函数可以完成大部分的数据输入输出功能:
 
// 处理字符串数据
extern int proc_dostring(ctl_table *, int, struct file *,
    void *, size_t *);
// 处理整数向量
extern int proc_dointvec(ctl_table *, int, struct file *,
    void *, size_t *);
// 处理整数向量,但init进程处理时稍有区别
extern int proc_dointvec_bset(ctl_table *, int, struct file *,
         void *, size_t *);
// 处理最大最小值形式的整数向量
extern int proc_dointvec_minmax(ctl_table *, int, struct file *,
    void *, size_t *);
// 处理最大最小值形式的无符合长整数向量
extern int proc_doulongvec_minmax(ctl_table *, int, struct file *,
      void *, size_t *);
// 处理整数向量,但用户数据作为秒数,转化为jiffies值,常用于时间控制
extern int proc_dointvec_jiffies(ctl_table *, int, struct file *,
     void *, size_t *);
// 处理无符合长整数向量,用户数据作为为毫秒值,转化为jiffies值,常用于时间控制
extern int proc_doulongvec_ms_jiffies_minmax(ctl_table *table, int,
          struct file *, void *, size_t *);

举例,以下代码取自net/ipv4/netfilter/ip_conntrack_standalone.c:
 
static ctl_table ip_ct_sysctl_table[] = {
 {NET_IPV4_NF_CONNTRACK_MAX, "ip_conntrack_max",
  &ip_conntrack_max, sizeof(int), 0644, NULL,
  &proc_dointvec},
 {NET_IPV4_NF_CONNTRACK_BUCKETS, "ip_conntrack_buckets",
  &ip_conntrack_htable_size, sizeof(unsigned int), 0444, NULL,
  &proc_dointvec},
 {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_SYN_SENT, "ip_conntrack_tcp_timeout_syn_sent",
  &ip_ct_tcp_timeout_syn_sent, sizeof(unsigned int), 0644, NULL,
  &proc_dointvec_jiffies},
......
 {0}
};

static ctl_table ip_ct_netfilter_table[] = {
 {NET_IPV4_NETFILTER, "netfilter", NULL, 0, 0555, ip_ct_sysctl_table, 0, 0, 0, 0, 0},
 {NET_IP_CONNTRACK_MAX, "ip_conntrack_max",
  &ip_conntrack_max, sizeof(int), 0644, NULL,
  &proc_dointvec},
 {0}
};
static ctl_table ip_ct_ipv4_table[] = {
 {NET_IPV4, "ipv4", NULL, 0, 0555, ip_ct_netfilter_table, 0, 0, 0, 0, 0},
 {0}
};
static ctl_table ip_ct_net_table[] = {
 {CTL_NET, "net", NULL, 0, 0555, ip_ct_ipv4_table, 0, 0, 0, 0, 0},
 {0}
};
static int init_or_cleanup(int init)
{
...
 ip_ct_sysctl_header = register_sysctl_table(ip_ct_net_table, 0);
...
}

有些/proc/sys的文件控制比较复杂,参数的输入实际是一个触发信息来执行一系列操作,这时这些缺省处理函数功能就不足了,就需要单独编写ctl_table结构中的proc_handle和strategy函数。如对于/proc/sys/net/ipv4 /ip_forward文件,对应的内核参数是ipv4_devconf.forwarding,如果该值改变,会将所有网卡设备的forwarding 属性值进行改变,定义如下:
 
/* net/ipv4/sysctl_net_ipv4.c */
static
int ipv4_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
   void *buffer, size_t *lenp)
{
// 保持当前的forwarding值
 int val = ipv4_devconf.forwarding;
 int ret;
// 完成/proc/sys的读写操作,如果是写操作,forwarding值已经改为新值
 ret = proc_dointvec(ctl, write, filp, buffer, lenp);
// 写操作,forwarding值改变,用新的forwarding值修改所有网卡的forwarding属性
 if (write && ipv4_devconf.forwarding != val)
  inet_forward_change(ipv4_devconf.forwarding);
 return ret;
}
static int ipv4_sysctl_forward_strategy(ctl_table *table, int *name, int nlen,
    void *oldval, size_t *oldlenp,
    void *newval, size_t newlen,
    void **context)
{
 int new;
 if (newlen != sizeof(int))
  return -EINVAL;
 if (get_user(new,(int *)newval))
  return -EFAULT;
 if (new != ipv4_devconf.forwarding)
  inet_forward_change(new);
// 把forwarding值赋值为新值后应该可以返回>0的数的,现在不赋值只能返回0继续了
// 不过该strategy函数好象不是必要的,上面的proc_handler函数已经可以处理了
 return 0; /* caller does change again and handles handles oldval */
}

ctl_table ipv4_table[] = {
......
        {NET_IPV4_FORWARD, "ip_forward",
         &ipv4_devconf.forwarding, sizeof(int), 0644, NULL,
         &ipv4_sysctl_forward,&ipv4_sysctl_forward_strategy},
......
 
4.2 释放函数
 
释放可读写的/proc文件使用unregister_sysctl_table()函数,该函数在kernel/sysctl.c中定义,声明如下:
void unregister_sysctl_table(struct ctl_table_header * header)
 
参数就是建立时的返回的struct ctl_table_header结构指针,通常在模块释放函数中调用。

5. 结论
 
内核中/proc编程现在已经很简单,将/proc目录作为单个的内核参数的控制是很合适的,但不适合大批量的数据传输。
分享到:
评论

相关推荐

    使用 /proc 文件系统来访问 Linux 内核的内容.rar

    /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux® 内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件...

    详解Linux用户态与内核态通信的几种方式

    我们平常在写代码时,一般是在用户空间,通过系统调用函数来访问内核空间,这是最常用的一种用户态和内核态通信的方式。(关于 Linux 用户态和内核态可以参考 xx) 除此之外,还有以下四种方式: procfs(/proc) ...

    Linux 2.6.19.x 内核编译配置选项简介

    通过netlink接口向用户空间导出任务/进程的统计信息,与BSD Process Accounting的不同之处在于这些统计信息在整个任务/进程生存期都是可用的 Enable per-task delay accounting 在统计信息中包含进程等候系统资源(cpu...

    linux proc

    用户空间与内核空间通信方法 /proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要...

    操作系统全部实验

    实验一 熟悉Linux环境 文件 实验二 进程控制 文件 实验三 单处理机系统的进程调度 文件 实验四 进程管道通信 文件 实验五 主存空间的分配和回收 文件 实验六 设备管理 文件 实验七 文件管理 //国外著名大学网址//...

    Linux内核源代码情景分析 (上下册 高清非扫描 )

    2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统...

    netlink实现分析

    netlink实现分析,内核与用户空间通信。

    linux 内核源代码分析

    2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2. 9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、...

    山东科技大学【操作系统实验】报告+全部源码(可运行)

    使用内存映射文件实现进程间通信 实验三:进程同步与互斥-生产者消费者(两种方式) 实验四:Limux内存管理 1.显示进程的虚拟内存地址空间分布信息 2.获取一个进程的虚拟存储区域信息 3.计算vma每个vma区域的大小,...

    Linux内核 内容很全

    硬件基础与软件基础 6 1.1 硬件基础 6 1.1.1 CPU 7 1.1.2 存储器 8 1.1.3 总线 8 1.1.4 控制器和外设 8 1.1.5 地址空间 9 1.1.6 时钟 9 1.2 软件基础 9 1.2.1 计算机语言 9 1.2.2 ...

    LINUX内核源代码情景分析(上).part1.rar

    2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2. 9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() . 第3章 中断...

    linux内核源代码情景分析

    《linux内核源代码情景分析》(非扫描电子版本) 第1章 预备知识 1.1 Linux内核简介 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的...

    Linux编程--Linux内核

    6.2.4 设备驱动程序与内核的接口 66 6.2.5 硬盘 69 6.2.6 网络设备 74 第7章 文件系统 77 7.1 第二个扩展文件系统EXT2 78 7.1.1 EXT2系统的inode节点 79 7.1.2 EXT2系统的超级块 80 7.1.3 EXT2系统的组描述符 80 ...

    Linux内核情景分析

    2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2. 9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和...

    嵌入式实验报告四.doc

    Bootloader一般还具有以下的功能:通讯功能 、FLASH相关功能、用户接口功能、Linux内核配置和编译的流程、Linux内核配置、Lin ux内核的编译 Kernel: 操作系统内核,通常运行进程,并提供进程间的通信;与系统结构...

    Linux内核情景分析(非扫描版)

    2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2. 9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() ...

    LINUX编程白皮书 (全集)

    6.2.4 设备驱动程序与内核的接口 66 6.2.5 硬盘 69 6.2.6 网络设备 74 第7章 文件系统 77 7.1 第二个扩展文件系统EXT2 78 7.1.1 EXT2系统的inode节点 79 7.1.2 EXT2系统的超级块 80 7.1.3 EXT2系统的组描述符 80 ...

    Linux程序设计参考书-六部

    第1章硬件基础与软件基础1.1 硬件基础1.1.1 CPU1.1.2 存储器1.1.3 总线1.1.4 控制器和外设1.1.5 地址空间1.1.6 时钟1.2 软件基础1.2.1 计算机语言1.2.1 计算机语言1.2.2 什么是操作系统第2章内存管理第3章进程第4章...

    基于Linux 的防火墙技术研究

    用户交互实现的,这就涉及一个如何与内核通信的问题。内核模块有三种办法与进程打交道:首先 是系统调用;第二种办法是通过设备文件;第三个办法便是使用proc 文件系统。netfilter 采用了第 一种修改系统调用的办法...

    Linux编程从入门到精通

    6.2.4 设备驱动程序与内核的接口 66 6.2.5 硬盘 69 6.2.6 网络设备 74 第7章 文件系统 77 7.1 第二个扩展文件系统EXT2 78 7.1.1 EXT2系统的inode节点 79 7.1.2 EXT2系统的超级块 80 7.1.3 EXT2系统的组描述符 80 ...

Global site tag (gtag.js) - Google Analytics