LinuxEye - Linux系统教程

LinuxEye - Linux系统教程

当前位置: 主页 > Linux教程 >

用户空间与内核的接口

时间:2012-11-16 09:59来源:CU 编辑:admin 点击:
内核通过不同的接口将信息输出到用户空间,除了用于请求特定信息的系统调用(system call)外,还有三个特殊接口: procfs(/proc文件系统):这是一个虚拟文件系统,挂载在/proc目录下
内核通过不同的接口将信息输出到用户空间,除了用于请求特定信息的系统调用(system call)外,还有三个特殊接口:
  1. procfs(/proc文件系统):这是一个虚拟文件系统,挂载在/proc目录下。允许内核以文件的形式向用户空间输出内部信息。这些文件并不实际存在于磁盘上,但可以通过cat或more以及‘>’shell重定向字符写入。也可以设置文件的访问权限。目录不能被写入,即用户不能把文件或目录添加到/proc中的任何目录或删除文件和目录。procfs不能编译成为一个模块。配置菜单中的相关内核选项为“Filesystems->pseudo filesystems->/proc file system support”
  2. sysctl(/proc/sys目录): 此接口允许用户读取或修改内核变量的值。不能用此接口对每个内核变量进行操作:内核应明确指出哪些变量从此接口是可见的。从用户空间可以用两种方式访问sysctl输出的变量,一是sysctl系统调用,另一种是procfs。当内核支持procfs时,会在/proc中添加一个特殊目录(/proc/sys),为每个由sysctl所输出的内核变量引入一个文件。procfs包随附的sysctl命令可用于配置由sysctl接口输出的变量,此命令通过写入/proc/sys与内核通信。sysctl不能编译成为一个模块。内核配置选项为“General setup –>sysctl support”。
  3. sysfs:以非常干净而有组织的方式输出很多信息。由sysctl所输出的部分信息可以移植到sysfs上。配置菜单中的内核选项为“Filesystem->pseudo filesystems->sysfs filesystem support(NEW)”,只有想开启“General setup->Configure standard kernel features(for small system)”选项后,才能看见上述选项。
  4. ioctl系统调用:ioctl系统调用操作的对象时一个文件,通常用于实现特殊设备需要而标准文件系统没有提供的操作。
  5. Netlink套接字:这是网络应用程序和内核通信时最新的首选机制。多数的网络内核功能都可以用Netlink或ioctl接口进行配置。

 

Procfs与sysctl

procfs和sysctl都输出内核内部信息,但procfs主要是输出只读数据,而大多数sysctl信息是可写入的,但只要超级用户能写入。与一个简单的内核变量或数据结构相关联的一些文件,可以用sysctl输出。其他涉及更为复杂的数据结构而需要特殊格式时,就以procfs输出,如缓存和统计数据。

Procfs

大多数网络功能会在引导时或在模块加载时其初始化期间在/proc中注册一个或多个文件。当用户读取该文件时,会引起内核间接运行一组内核函数,返回某种输出内容。网络代码所注册的文件位于/proc/net。
/proc中的目录可以使用proc_mkdir创建。/proc/net中的文件可以使用定义在include/linux/proc_fs.h中的proc_net_fops_create和proc_net_remove予以注册和注销。
还有两个通用的API函数create_proc_entry和remove_proc_entry。proc_net_fops_create负责调用proc_net_create创建文档,然后初始化器文件操作处理函数。
如ARP协议在/proc/net中注册其arp文件:
static const struct file_operations arp_seq_fops = {
.owner = THIS_MODULE,
.open = arp_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};

static int __net_init arp_net_init(struct net *net)
{
if (!proc_net_fops_create(net, "arp", S_IRUGO, &arp_seq_fops))
return -ENOMEM;
return 0;
}

static void __net_exit arp_net_exit(struct net *net)
{
proc_net_remove(net, "arp");
}

proc_net_fops_create参数中权限必须指定为只读。当用户读取该文件时,使用file_operations数据结构,返回数据给用户。open所初始化的例程如前例中的qrp_seq_open会做另一次重要的初始化:注册一个函数指针数组,包括procfs用于变量要传回给用户数据的所有例程:一个例程启动dump复制数据,另一个索引到下一条记录,在dump该条记录,继续后移。这些例程的内部保存必要的环境信息,就是已经dump了多少信息。这些信息是dump点已经到正确位置继续dump所必需的。

static const struct seq_operations arp_seq_ops = {
.start = arp_seq_start,
.next = neigh_seq_next,
.stop = neigh_seq_stop,
.show = arp_seq_show,
};

static int arp_seq_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &arp_seq_ops,
sizeof(struct neigh_seq_state));
}

Sysctl:目录/proc/sys

在/proc/sys下看到的一个文件,实际都是一个内核变量。对于每个变量,内核可以将其放在/proc/sys的位置(与相同内核组件或功能相关联的变量通常都位于同一个目录中,如/proc/sys/net/ipv4目录中都是与ipv4相关的文件)、命名(多数时候文件名都是和相关联的内核变量相同的名字)、访问权限(如一个文件可以由任何人读,但只能由超级用户修改)。
输出到/proc/sys中的变量内容可借助相关联的文件进行读写或直接用sysctl系统调用。有些目录和文件在引导期间静态定义,而其他则是在允许期间添加的。当一个内核模块实现一项新功能或一个协议被加载或卸载时;当一个新的网络设备被注册或注销时。都有可能创建目录或文件。
/proc/sys中的文件和目录都是以ctl_table结构定义的。ctl_table结构的注册和注销是通过调用kernel/sysctl.c中定义的register_sysctl_table和unregister_sysctl_table:

struct ctl_table_header *register_sysctl_table(struct ctl_table *table);
void unregister_sysctl_table(struct ctl_table_header * header);
struct ctl_table
{
    const char *procname;        /* Text ID for /proc/sys, or zero */
    void *data;
    int maxlen;
    mode_t mode;
    struct ctl_table *child;
    struct ctl_table *parent;    /* Automatically set */
    proc_handler *proc_handler;    /* Callback for text formatting */
    void *extra1;
    void *extra2;
};

ctl_table的关键成员:
procname:在/proc/sys中所使用的文件名
maxlen:输出的内核变量的大小
mode:创建的/proc/sys猪相关文件或目录的访问权限
child:用于建立目录和文件直接的父子关系
proc_handler:当在/proc/sys中读取或写入一个文件时,完成读取或写入操作的函数。所有和文件相关联的ctl_instance都必须有proc_handler初始化,内内核会给目录分派一个默认值
extra1,extra2:可选参数,通常用于定义变量的最小值和最大值

根据与文件相关联的变量类型的不同,proc_handler所指的函数也不相同。
proc_dostring:读/写一个字符串
proc_dointvec:读写一个包含一个或多个整数的数组
proc_dointvec_minmax:同上,但是要确定输入数据在min/max范围内,不在该范围内的值会被拒绝
proc_dointvec_jiffies:读写一个整数数组,但此内核变量以jiffies为单位表示,在返回用户前会转化为秒数,写入前转化为jiffies
proc_dointvec_ms_jiffies:同上,只是这里是转化为毫秒数
proc_doulongvec_minmax:类似proc_dointvec_minmax,但其值为长整数。
proc_doulongvec_ms_jiffies_minmax:读取一个长整数数组。此内核变量以jiffies为单位,而用户空间已毫秒为单位。此值也必须指定min和max区间

ctl_table注册文件的结构实例:
static struct ctl_table ctl_forward_entry[] = {
    {
        .procname    = "ip_forward", //文件名
       .data        = &ipv4_devconf.data[
                    IPV4_DEVCONF_FORWARDING - 1], //输出参数
       .maxlen        = sizeof(int), //参数大小
       .mode        = 0644, //文件权限
       .proc_handler    = devinet_sysctl_forward, //指定函数
       .extra1        = &ipv4_devconf,
        .extra2        = &init_net,
    },
    { },
};

在这里还看不出这个文件在/proc/sys下的那个目录中。
这里是/proc/sys目录下建的默认目录:
static struct ctl_table root_table[] = {
    {
        .procname    = "kernel",
        .mode        = 0555,
        .child        = kern_table,
    },
    {
        .procname    = "vm",
        .mode        = 0555,
        .child        = vm_table,
    },
    {
        .procname    = "fs",
        .mode        = 0555,
        .child        = fs_table,
    },
    {
        .procname    = "debug",
        .mode        = 0555,
        .child        = debug_table,
    },
    {
        .procname    = "dev",
        .mode        = 0555,
        .child        = dev_table,
    },
    { }
};

目录不需要proc_handler函数指针但却有一个child字段。child是一个指针,指向另一个ctl_table结构,这个地址是ctl_table结构列表的头元素地址

 

在/proc/sys中注册文件

函数register_sysctl_table的输入并不包括输入参数ctl_table应该被添加到/proc/sys文件目录的那个子目录中。原因在于所有的插入都是针对/proc/sys目录进行的,所以若想将一个文件注册到/proc/sys的子目录中,就必须建立一棵树由多个child字段链接成ctl_table实体以提供完整路径,然后将代表刚健的树根的ctl_table传递给register_sysctl_table函数。

drivers/scsi/scsi_sysctl.c显示了文件logging_level的定义已经放置到/proc/sys/dev/scsi/目录下的方法:

static ctl_table scsi_table[] = {                                    //logging_level文件
       .procname    = "logging_level",
      .data        = &scsi_logging_level,
      .maxlen    = sizeof(scsi_logging_level),
      .mode        = 0644,
      .proc_handler    = proc_dointvec },
    { }
};

static ctl_table scsi_dir_table[] = {                 //scsi目录
    { .procname    = "scsi",
      .mode        = 0555,
      .child    = scsi_table },
    { }
};

static ctl_table scsi_root_table[] = {      //dev目录
    { .procname    = "dev",
      .mode        = 0555,
      .child    = scsi_dir_table },
    { }
};

static struct ctl_table_header *scsi_table_header;

int __init scsi_init_sysctl(void) //注册
{
    scsi_table_header = register_sysctl_table(scsi_root_table);
    if (!scsi_table_header)
        return -ENOMEM;
    return 0;
}

void scsi_exit_sysctl(void) //注销
{
    unregister_sysctl_table(scsi_table_header);
}

若需要添加多个文件到同一个目录下,可以定义一个模板,每次有新文件要添加到同一个目录下就重用。使用模板的好处是ctl_table结构只需要初始化一个便可贯穿整个目录。

转载请保留固定链接: https://linuxeye.com/Linux/931.html

------分隔线----------------------------
标签:内核用户空间
栏目列表
推荐内容