在linux 下用户空间与内核空间数据交换的方式_第1页
在linux 下用户空间与内核空间数据交换的方式_第2页
在linux 下用户空间与内核空间数据交换的方式_第3页
在linux 下用户空间与内核空间数据交换的方式_第4页
在linux 下用户空间与内核空间数据交换的方式_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一、引言一般地,在使用虚拟内存技术的多任务系统上,内核和应用有不同的地址空间,因此,在内核和应用之间以及在应用与应用之间进行数据交换需要专门的机制来实现,众所周知,进程间通信(IPC)机制就是为实现应用与应用之间的数据交换而专门实现的,大部分读者可能对进程间通信比较了解,但对应用与内核之间的数据交换机制可能了解甚少,本文将详细介绍 Linux 系统下内核与应用进行数据交换的各种方式,包括内核启动参数、模块参数与 sysfs、sysctl、系统调用、netlink、procfs、seq_file、debugfs 和 relayfs。回页首二、内核启动参数Linux 提供了一种通过 bootloader 向其传输启动参数的功能,内核开发者可以通过这种方式来向内核传输数据,从而控制内核启动行为。通常的使用方式是,定义一个分析参数的函数,而后使用内核提供的宏 _setup 把它注册到内核中,该宏定义在 linux/init.h 中,因此要使用它必须包含该头文件:_setup(“para_name=“, parse_func)para_name 为参数名,parse_func 为分析参数值的函数,它负责把该参数的值转换成相应的内核变量的值并设置那个内核变量。内核为整数参数值的分析提供了函数 get_option 和 get_options,前者用于分析参数值为一个整数的情况,而后者用于分析参数值为逗号分割的一系列整数的情况,对于参数值为字符串的情况,需要开发者自定义相应的分析函数。在源代码包中的内核程序kern-boot-params.c 说明了三种情况的使用。该程序列举了参数为一个整数、逗号分割的整数串以及字符串三种情况,读者要想测试该程序,需要把该程序拷贝到要使用的内核的源码目录树的一个目录下,为了避免与内核其他部分混淆,作者建议在内核源码树的根目录下创建一个新目录,如 examples,然后把该程序拷贝到 examples 目录下并重新命名为 setup_example.c,并且为该目录创建一个 Makefile 文件:obj-y = setup_example.oMakefile 仅许这一行就足够了,然后需要修改源码树的根目录下的 Makefile文件的一行,把下面行core-y := usr/修改为core-y := usr/examples/注意:如果读者创建的新目录和重新命名的文件名与上面不同,需要修改上面所说 Makefile 文件相应的位置。做完以上工作就可以按照内核构建步骤去构建新的内核,在构建好内核并设置好 lilo 或 grub 为该内核的启动条目后,就可以启动该内核,然后使用 lilo 或 grub 的编辑功能为该内核的启动参数行增加如下参数串:setup_example_int=1234 setup_example_int_array=100,200,300,400 setup_example_string=Thisisatest当然,该参数串也可以直接写入到 lilo 或 grub 的配置文件中对应于该新内核的内核命令行参数串中。读者可以使用其它参数值来测试该功能。下面是作者系统上使用上面参数行的输出:setup_example_int=1234setup_example_int_array=100,200,300,400setup_example_int_array includes 4 intergerssetup_example_string=Thisisatest读者可以使用dmesg | grep setup来查看该程序的输出。回页首三、模块参数与 sysfs内核子系统或设备驱动可以直接编译到内核,也可以编译成模块,如果编译到内核,可以使用前一节介绍的方法通过内核启动参数来向它们传递参数,如果编译成模块,则可以通过命令行在插入模块时传递参数,或者在运行时,通过sysfs 来设置或读取模块数据。Sysfs 是一个基于内存的文件系统,实际上它基于 ramfs,sysfs 提供了一种把内核数据结构,它们的属性以及属性与数据结构的联系开放给用户态的方式,它与 kobject 子系统紧密地结合在一起,因此内核开发者不需要直接使用它,而是内核的各个子系统使用它。用户要想使用 sysfs 读取和设置内核参数,仅需装载 sysfs 就可以通过文件操作应用来读取和设置内核通过 sysfs 开放给用户的各个参数:$ mkdir -p /sysfs$ mount -t sysfs sysfs /sysfs注意,不要把 sysfs 和 sysctl 混淆,sysctl 是内核的一些控制参数,其目的是方便用户对内核的行为进行控制,而 sysfs 仅仅是把内核的 kobject 对象的层次关系与属性开放给用户查看,因此 sysfs 的绝大部分是只读的,模块作为一个 kobject 也被出口到 sysfs,模块参数则是作为模块属性出口的,内核实现者为模块的使用提供了更灵活的方式,允许用户设置模块参数在 sysfs 的可见性并允许用户在编写模块时设置这些参数在 sysfs 下的访问权限,然后用户就可以通过 sysfs 来查看和设置模块参数,从而使得用户能在模块运行时控制模块行为。对于模块而言,声明为 static 的变量都可以通过命令行来设置,但要想在 sysfs 下可见,必须通过宏 module_param 来显式声明,该宏有三个参数,第一个为参数名,即已经定义的变量名,第二个参数则为变量类型,可用的类型有 byte, short, ushort, int, uint, long, ulong, charp 和 bool 或 invbool,分别对应于 c 类型 char, short, unsigned short, int, unsigned int, long, unsigned long, char * 和 int,用户也可以自定义类型 XXX(如果用户自己定义了 param_get_XXX,param_set_XXX 和 param_check_XXX)。该宏的第三个参数用于指定访问权限,如果为 0,该参数将不出现在 sysfs 文件系统中,允许的访问权限为 S_IRUSR, S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH 和 S_IWOTH 的组合,它们分别对应于用户读,用户写,用户组读,用户组写,其他用户读和其他用户写,因此用文件的访问权限设置是一致的。在源代码包中的内核模块 module-param-exam.c 是一个利用模块参数和 sysfs来进行用户态与内核态数据交互的例子。该模块有三个参数可以通过命令行设置,下面是作者系统上的运行结果示例:$ insmod ./module-param-exam.ko my_invisible_int=10 my_visible_int=20 mystring=“Hello,World“my_invisible_int = 10my_visible_int = 20mystring = Hello,World$ ls /sys/module/module_param_exam/parameters/mystring my_visible_int$ cat /sys/module/module_param_exam/parameters/mystringHello,World$ cat /sys/module/module_param_exam/parameters/my_visible_int20$ echo 2000 /sys/module/module_param_exam/parameters/my_visible_int$ cat /sys/module/module_param_exam/parameters/my_visible_int2000$ echo “abc“ /sys/module/module_param_exam/parameters/mystring$ cat /sys/module/module_param_exam/parameters/mystringabc$ rmmod module_param_exammy_invisible_int = 10my_visible_int = 2000mystring = abc回页首四、sysctlSysctl 是一种用户应用来设置和获得运行时内核的配置参数的一种有效方式,通过这种方式,用户应用可以在内核运行的任何时刻来改变内核的配置参数,也可以在任何时候获得内核的配置参数,通常,内核的这些配置参数也出现在proc 文件系统的/proc/sys 目录下,用户应用可以直接通过这个目录下的文件来实现内核配置的读写操作,例如,用户可以通过Cat /proc/sys/net/ipv4/ip_forward来得知内核 IP 层是否允许转发 IP 包,用户可以通过echo 1 /proc/sys/net/ipv4/ip_forward把内核 IP 层设置为允许转发 IP 包,即把该机器配置成一个路由器或网关。一般地,所有的 Linux 发布也提供了一个系统工具 sysctl,它可以设置和读取内核的配置参数,但是该工具依赖于 proc 文件系统,为了使用该工具,内核必须支持 proc 文件系统。下面是使用 sysctl 工具来获取和设置内核配置参数的例子:$ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 0$ sysctl -w net.ipv4.ip_forward=1net.ipv4.ip_forward = 1$ sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1注意,参数 net.ipv4.ip_forward 实际被转换到对应的 proc 文件/proc/sys/net/ipv4/ip_forward,选项 -w 表示设置该内核配置参数,没有选项表示读内核配置参数,用户可以使用 sysctl -a 来读取所有的内核配置参数,对应更多的 sysctl 工具的信息,请参考手册页 sysctl(8)。但是 proc 文件系统对 sysctl 不是必须的,在没有 proc 文件系统的情况下,仍然可以,这时需要使用内核提供的系统调用 sysctl 来实现对内核配置参数的设置和读取。在源代码包中给出了一个实际例子程序,它说明了如何在内核和用户态使用sysctl。头文件 sysctl-exam.h 定义了 sysctl 条目 ID,用户态应用和内核模块需要这些 ID 来操作和注册 sysctl 条目。内核模块在文件 sysctl-exam-kern.c 中实现,在该内核模块中,每一个 sysctl 条目对应一个 struct ctl_table 结构,该结构定义了要注册的 sysctl 条目的 ID(字段 ctl_name),在 proc 下的名称(字段 procname),对应的内核变量(字段 data,注意该该字段的赋值必须是指针),条目允许的最大长度(字段 maxlen,它主要用于字符串内核变量,以便在对该条目设置时,对超过该最大长度的字符串截掉后面超长的部分),条目在 proc 文件系统下的访问权限(字段 mode),在通过proc 设置时的处理函数(字段 proc_handler,对于整型内核变量,应当设置为2. 注册定义的伪字符设备并把它和上面的 struct file_operations 关联起来:int exam_char_dev_major;exam_char_dev_major = register_chrdev(0, “exam_char_dev“, 注意,函数 register_chrdev 的第一个参数如果为 0,表示由内核来确定该注册伪字符设备的主设备号,这是该函数的返回为实际分配的主设备号,如果返回小于 0,表示注册失败。因此,用户在使用该函数时必须判断返回值以便处理失败情况。为了使用该函数必须包含头文件 linux/fs.h。在源代码包中给出了一个使用这种方式实现用户态与内核态数据交换的典型例子,它包含了三个文件:头文件 syscall-exam.h 定义了 ioctl 命令,.c 文件 syscall-exam-user.c 为用户态应用,它通过文件系统操作函数 mmap 和 ioctl 来与内核态模块交换数据,.c 文件 syscall-exam-kern.c 为内核模块,它实现了一个伪字符设备,以便与用户态应用进行数据交换。为了正确运行应用程序 syscall-exam-user,需要在插入模块 syscall-exam-kern 后创建该实现的伪字符设备,用户可以使用下面命令来正确创建设备:$ mknod /dev/mychrdev c dmesg | grep “char device mychrdev“ | sed s/.*major is /g 0然后用户可以通过 cat 来读写 /dev/mychrdev,应用程序 syscall-exam-user则使用 mmap 来读数据并使用 ioctl 来得到该字符设备的信息以及裁减数据内容,它只是示例如何使用现有的系统调用来实现用户需要的数据交互操作。下面是作者运行该模块的结果示例:$ insmod ./syscall-exam-kern.kochar device mychrdev is registered, major is 254$ mknod /dev/mychrdev c dmesg | grep “char device mychrdev“ | sed s/.*major is /g 0$ cat /dev/mychrdev$ echo “abcdefghijklmnopqrstuvwxyz“ /dev/mychrdev$ cat /dev/mychrdevabcdefghijklmnopqrstuvwxyz$ ./syscall-exam-userUser process: syscall-exam-us(1433)Available space: 65509 bytesData len: 27 bytesOffset in physical: cc0 bytesmychrdev content by mmap:abcdefghijklmnopqrstuvwxyz$ cat /dev/mychrdevabcde$回页首六、netlinkNetlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在最新的 Linux 内核(2.6.14)中使用 netlink 进行应用与内核通信的应用很多,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系统(NETLINK_W1),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),socket 监视(NETLINK_INET_DIAG),netfilter 日志(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系统(NETLINK_ISCSI),进程审计(NETLINK_AUDIT),转发信息表查询(NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系统(NETLINK_NETFILTER),IPv6 防火墙(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)。Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。2. netlink 是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket 缓存队列中,发送消息只是把消息保存在接收者的 socket 的接收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。3使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。4netlink 支持多播,内核模块或应用可以把消息多播给一个 netlink 组,属于该 neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在后面的文章中将介绍这一机制的使用。5内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。6netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。用户态使用 netlink用户态应用使用标准的 socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,查询手册页可以了解这些函数的使用细节,本文只是讲解使用 netlink 的用户应该如何使用这些函数。注意,使用 netlink 的应用必须包含头文件 linux/netlink.h。当然 socket 需要的头文件也必不可少,sys/socket.h。为了创建一个 netlink socket,用户需要使用如下参数调用 socket():socket(AF_NETLINK, SOCK_RAW, netlink_type)第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用 netlink,第二个参数必须是 SOCK_RAW 或SOCK_DGRAM, 第三个参数指定 netlink 协议类型,如前面讲的用户自定义协议类型 NETLINK_MYTEST, NETLINK_GENERIC 是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。内核预定义的协议类型有:#define NETLINK_ROUTE 0 /* Routing/device hook */#define NETLINK_W1 1 /* 1-wire subsystem */#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */#define NETLINK_FIREWALL 3 /* Firewalling hook */#define NETLINK_INET_DIAG 4 /* INET socket monitoring */#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */#define NETLINK_XFRM 6 /* ipsec */#define NETLINK_SELINUX 7 /* SELinux event notifications */#define NETLINK_ISCSI 8 /* Open-iSCSI */#define NETLINK_AUDIT 9 /* auditing */#define NETLINK_FIB_LOOKUP 10#define NETLINK_CONNECTOR 11#define NETLINK_NETFILTER 12 /* netfilter subsystem */#define NETLINK_IP6_FW 13#define NETLINK_DNRTMSG 14 /* DECnet routing messages */#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */#define NETLINK_GENERIC 16对于每一个 netlink 协议类型,可以有多达 32 多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一起。netlink socket 的地址结构如下:struct sockaddr_nlsa_family_t nl_family;unsigned short nl_pad;_u32 nl_pid;_u32 nl_groups;字段 nl_family 必须设置为 AF_NETLINK 或着 PF_NETLINK,字段 nl_pad 当前没有使用,因此要总是设置为 0,字段 nl_pid 为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。字段 nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组。传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个进程的多个线程使用 netlink socket 的情况,字段 nl_pid 则可以设置为其它的值,如:pthread_self() nlmsg_len = NLMSG_LENGTH(strlen(buffer);nlhdr-nlmsg_pid = getpid(); /* self pid */nlhdr-nlmsg_flags = 0;结构 struct iovec 用于把多个消息通过一次系统调用来发送,下面是该结构使用示例:struct iovec iov;iov.iov_base = (void *)nlhdr;iov.iov_len = nlh-nlmsg_len;msg.msg_iov = msg.msg_iovlen = 1;在完成以上步骤后,消息就可以通过下面语句直接发送:sendmsg(fd, 应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,添完后就可以直接调用函数 recvmsg() 来接收。#define MAX_NL_MSG_LEN 1024struct sockaddr_nl nladdr;struct msghdr msg;struct iovec iov;struct nlmsghdr * nlhdr;nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);iov.iov_base = (void *)nlhdr;iov.iov_len = MAX_NL_MSG_LEN;msg.msg_name = (void *)msg.msg_namelen = sizeof(nladdr);msg.msg_iov = msg.msg_iovlen = 1;recvmsg(fd, 注意:fd 为 socket 调用打开的 netlink socket 描述符。在消息接收后,nlhdr 指向接收到的消息的消息头,nladdr 保存了接收到的消息的目标地址,宏 NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。在 linux/netlink.h 中定义了一些方便对消息进行处理的宏,这些宏包括:#define NLMSG_ALIGNTO 4#define NLMSG_ALIGN(len) ( (len)+NLMSG_ALIGNTO-1) 参数 unit 表示 netlink 协议类型,如 NETLINK_MYTEST,参数 input 则为内核模块定义的 netlink 消息处理函数,当有消息到达这个 netlink socket 时,该input 函数指针就会被引用。函数指针 input 的参数 sk 实际上就是函数netlink_kernel_create 返回的 struct sock 指针,sock 实际是 socket 的一个内核表示数据结构,用户态应用创建的 socket 在内核中也会有一个 struct sock 结构来表示。下面是一个 input 函数的示例:void input (struct sock *sk, int len)struct sk_buff *skb;struct nlmsghdr *nlh = NULL;u8 *data = NULL;while (skb = skb_dequeue(data = NLMSG_DATA(nlh);/* process netlink message with header pointed by * nlh and data pointed by data*/ 函数 input()会在发送进程执行 sendmsg()时被调用,这样处理消息比较及时,但是,如果消息特别长时,这样处理将增加系统调用 sendmsg()的执行时间,对于这种情况,可以定义一个内核线程专门负责消息接收,而函数 input 的工作只是唤醒该内核线程,这样 sendmsg 将很快返回。函数 skb = skb_dequeue(当内核中发送 netlink 消息时,也需要设置目标地址与源地址,而且内核中消息是通过 struct sk_buff 来管理的, linux/netlink.h 中定义了一个宏:#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)NETLINK_CB(skb).dst_pid = 0;NETLINK_CB(skb).dst_group = 1;字段 pid 表示消息发送者进程 ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。在内核中,模块调用函数 netlink_unicast 来发送单播消息:int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);参数 sk 为函数 netlink_kernel_create()返回的 socket,参数 skb 存放消息,它的 data 字段指向要发送的 netlink 消息结构,而 skb 的控制块保存了消息的地址信息,前面的宏 NETLINK_CB(skb)就用于方便设置该控制块, 参数 pid 为接收消息进程的 pid,参数 nonblock 表示该函数是否为非阻塞,如果为 1,该函数将在没有接收缓存可利用时立即返回,而如果为 0,该函数在没有接收缓存可利用时睡眠。内核模块或子系统也可以使用函数 netlink_broadcast 来发送广播消息:void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);前面的三个参数与 netlink_unicast 相同,参数 group 为接收消息的多播组,该参数的每一个代表一个多播组,因此如果发送给多个多播组,就把该参数设置为多个多播组组 ID 的位或。参数 allocation 为内核内存分配类型,一般地为 GFP_ATOMIC 或 GFP_KERNEL,GFP_ATOMIC 用于原子的上下文(即不可以睡眠),而 GFP_KERNEL 用于非原子上下文。在内核中使用函数 sock_release 来释放函数 netlink_kernel_create()创建的netlink socket:void sock_release(struct socket * sock);注意函数 netlink_kernel_create()返回的类型为 struct sock,因此函数sock_release 应该这种调用:sock_release(sk-sk_socket);sk 为函数 netlink_kernel_create()的返回值。在源代码包中给出了一个使用 netlink 的示例,它包括一个内核模块 netlink-exam-kern.c 和两个应用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。内核模块必须先插入到内核,然后在一个终端上运行用户态接收程序,在另一个终端上运行用户态发送程序,发送程序读取参数指定的文本文件并把它作为 netlink 消息的内容发送给内核模块,内核模块接受该消息保存到内核缓存中,它也通过 proc 接口出口到 procfs,因此用户也能够通过 /proc/netlink_exam_buffer 看到全部的内容,同时内核也把该消息发送给用户态接收程序,用户态接收程序将把接收到的内容输出到屏幕上。回页首小结本文是系列文章的第一篇,它详细介绍了五种用户空间与内核空间的数据交换方式,并通过实际例子程序向读者讲解了如何在内核开发中使用这些技术,其中内核启动参数方式是单向的,即只能向内核传递,而不能从内核获取,其余的均可以进行双向数据交换,即既可以从用户应用传递给内核,有可以从内核传递给应用态应用。netlink 是一种双向的数据交换方式,它使用起来非常简单高效,特别是它的广播特性在一些应用中非常方便。作者认为,它是所有这些用户态与内核态数据交换方式中最有效的最强大的方式。该系列文章的第二篇将详细地讲解另外三种用户态与内核态的数据交换方式,包括 procfs、seq_file、debugfs 和 relayfs,有兴趣的读者请参看该系列文章第二篇。本文是该系列文章的第二篇,他介绍了 procfs、seq_file、 debugfs 和 relayfs,并结合给出的例子程式周详地说明了他们怎么使用。一、procfsprocfs 是比较老的一种用户态和内核态的数据交换方式,内核的非常多数据都是通过这种方式出口给用户的,内核的非常多参数也是通过这种方式来让用户方便设置的。除了 sysctl 出口到 /proc 下的参数,procfs 提供的大部分内核参数是只读的。实际上,非常多应用严重地依赖于 procfs,因此他几乎是必不可少的组件。前面部分的几个例子实际上已使用他来出口内核数据,不过并没有讲解怎么使用,本节将讲解怎么使用procfs。Procfs 提供了如下 API:struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,struct proc_dir_entry *parent)该函数用于创建一个正常的 proc 条目,参数 name 给出要建立的 proc 条目的名称,参数mode 给出了建立的该 proc 条目的访问权限,参数 parent 指定建立的 proc 条目所在的目录。如果要在/proc 下建立 proc 条目,parent 应当为 NULL。否则他应当为 proc_mkdir返回的 struct proc_dir_entry 结构的指针。extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)该函数用于删除上面函数创建的 proc 条目,参数 name 给出要删除的 proc 条目的名称,参数 parent 指定建立的 proc 条目所在的目录。struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)该函数用于创建一个 proc 目录,参数 name 指定要创建的 proc 目录的名称,参数 parent为该 proc 目录所在的目录。extern struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode,struct proc_dir_entry *parent);struct proc_dir_entry *proc_symlink(const char * name,struct proc_dir_entry * parent, const char * dest)该函数用于建立一个 proc 条目的符号链接,参数 name 给出要建立的符号链接 proc 条目的名称,参数 parent 指定符号连接所在的目录,参数 dest 指定链接到的 proc 条目名称。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)该函数用于建立一个规则的只读 proc 条目,参数 name 给出要建立的 proc 条目的名称,参数 mode 给出了建立的该 proc 条目的访问权限,参数 base 指定建立的 proc 条目所在的目录,参数 read_proc 给出读去该 proc 条目的操作函数,参数 data 为该 proc 条目的专用数据,他将保存在该 proc 条目对应的 struct file 结构的 private_data 字段中。struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)该函数用于创建一个 info 型的 proc 条目,参数 name 给出要建立的 proc 条目的名称,参数mode 给出了建立的该 proc 条目的访问权限,参数 base 指定建立的 proc 条目所在的目录,参数 get_info 指定该 proc 条目的 get_info 操作函数。实际上 get_info 等同于 read_proc,如果 proc 条目没有定义个 read_proc,对该 proc 条目的 read 操作将使用 get_info 取代,因此他在功能上非常类似于函数 create_proc_read_entry。struct proc_dir_entry *proc_net_create(const char *name,mode_t mode, get_info_t *get_info)该函数用于在/proc/net 目录下创建一个 proc 条目,参数 name 给出要建立的 proc 条目的名称,参数 mode 给出了建立的该 proc 条目的访问权限,参数 get_info 指定该 proc 条目的get_info 操作函数。struct proc_dir_entry *proc_net_fops_create(const char *name,mode_t mode, struct file_operations *fops)该函数也用于在/proc/net 下创建 proc 条目,不过他也同时指定了对该 proc 条目的文件操作函数。void proc_net_remove(const char *name)该函数用于删除前面两个函数在/proc/net 目录下创建的 proc 条目。参数 name 指定要删除的 proc 名称。除了这些函数,值得一提的是结构 structproc_dir_entry,为了创建一了可写的 proc 条目并指定该 proc 条目的写操作函数,必须设置上面的这些创建 proc 条目的函数返回的指针指向的 struct proc_dir_entry 结构的 write_proc 字段,并指定该 proc 条目的访问权限有写权限。为了使用这些接口函数及结构 struct proc_dir_entry,用户必须在模块中包含头文件linux/proc_fs.h。在原始码包中给出了 procfs 示例程式 procfs_exam.c,他定义了三个 proc 文件条目和一个proc 目录条目,读者在插入该模块后应当看到如下结构:$ ls /proc/myproctestaint astring bigprocfile$读者能通过 cat 和 echo 等文件操作函数来查看和设置这些 proc 文件。特别需要指出,bigprocfile 是个大文件(超过一个内存页) ,对于这种大文件,procfs 有一些限制,因为他提供的缓存,只有一个页,因此必须特别小心,并对超过页的部分做特别的考虑,处理起来比较复杂并且非常容易出错,所有 procfs 并不适合于大数据量的输入输出,后面一节seq_file 就是因为这一缺陷而设计的,当然 seq_file 依赖于 procfs 的一些基础功能。二、seq_file一般地,内核通过在 procfs 文件系统下建立文件来向用户空间提供输出信息,用户空间能通过所有文本阅读应用查看该文件信息,不过 procfs 有一个缺陷,如果输出内容大于 1 个内存页,需要多次读,因此处理起来非常难,另外,如果输出太大,速度比较慢,有时会出现一些意想不到的情况,AlexanderViro 实现了一套新的功能,使得内核输出大文件信息更容易,该功能出目前 2.4.15(包括2.4.15)以后的所有 2.4 内核及 2.6 内核中,尤其是在 2.6 内核中,已大量地使用了该功能。要想使用 seq_file 功能,研发者需要包含头文件 linux/seq_file.h,并定义和设置一个seq_operations 结构(类似于 file_operations 结构):struct seq_operations void * (*start) (struct seq_file *m, loff_t *pos);void (*stop) (struct seq_file *m, void *v);void * (*next) (struct seq_file *m, void *v, loff_t *pos);int (*show) (struct seq_file *m, void *v);start 函数用于指定 seq_file 文件的读开始位置,返回实际读开始位置,如果指定的位置超过文件末尾,应当返回 NULL,start 函数可以有一个特别的返回 SEQ_START_TOKEN,他用于让 show 函数输出文件头,但这只能在pos 为 0 时使用, next 函数用于把 seq_file文件的当前读位置移动到下一个读位置,返回实际的下一个读位置,如果已到达文件末尾,返回 NULL,stop 函数用于在读完 seq_file 文件后调用,他类似于文件操作 close,用于做一些必要的清理,如释放内存等, show 函数用于格式化输出,如果成功返回 0,否则返回出错码。Seq_file 也定义了一些辅助函数用于格式化输出:int seq_putc(struct seq_file *m, char c);函数 seq_putc 用于把一个字符输出到 seq_file 文件。int seq_puts(struct seq_file *m, const char *s);函数 seq_puts 则用于把一个字符串输出到 seq_file 文件。int seq_escape(struct seq_file *, const char *, const char *);函数 seq_escape 类似于 seq_puts,只是,他将把第一个字符串参数中出现的包含在第二个字符串参数中的字符按照八进制形式输出,也即对这些字符进行转义处理。int seq_printf(struct seq_file *, const char *, .)_attribute_ (format (printf,2,3);函数 seq_printf 是最常用的输出函数,他用于把给定参数按照给定的格式输出到 seq_file 文件。int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);函数 seq_path 则用于输出文件名,字符串参数提供需要转义的文件名字符,他主要供文件系统使用。在定义了结构 struct seq_operations 之后,用户还需要把打开 seq_file 文件的 open 函数,以便该结构和对应于 seq_file 文件的 struct file 结构关联起来,例如, struct seq_operations定义为:struct seq_operations exam_seq_ops = .start = exam_seq_start,.stop = exam_seq_s

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论