存档在 ‘Linux内核源码分析’ 分类

inotify机制在内核态下的使用方法

2011年10月5日

inotify是一种使用简单而功能强大的文件系统事件监控机制。在用户态下通过一组简单的系统调用即可使用inotify机制监控文件的变化,而在内核态中也可以通过一组API来inotify机制。

1.创建并初始化inotify实例

为了在内核中使用inotify机制,必须首先创建并初始化一个inotify实例,每个inotify实例其实对应的是一个数据结构inotify_handle。

struct inotify_handle {
	struct idr		idr;		/* idr mapping wd -> watch */
	struct mutex		mutex;		/* protects this bad boy */
	struct list_head	watches;	/* list of watches */
	atomic_t		count;		/* reference count */
	u32			last_wd;	/* the last wd allocated */
	const struct inotify_operations *in_ops; /* inotify caller operations */
};

创建并初始化一个inotify实例的过程其实就是分配并初始化一个inotify_handle结构,这个过程通过inotify_init()完成。此外,每个inotify实例都与一个inotify_operations结构相关联,该结构中有两个钩子函数,分别定义了事件处理和销毁watch两个函数的接口,这两个钩子函数应该根据具体的应用场景来实现。

struct inotify_operations {
	void (*handle_event)(struct inotify_watch *, u32, u32, u32,
			     const char *, struct inode *);
	void (*destroy_watch)(struct inotify_watch *);
};

一个inotify_init()通用的使用方法如下:

struct inotify_operations *ops;
ops->handle_event = my_handle_event;
ops->destroy_watch = my_destroy_watch;
struct inotify_handle *ih = inotify_init(ops);

也就是说,通过向inotify_init()中传入一个inotify_operations结构的变量来实现钩子函数和inotify实例之间的关联。

2.添加watch

从inotify实例所对应的数据结构inotify_handle中可以看出,每个inotify实例都拥有一个由watch组成的链表。该双链表上的每个watch即代表该inotify实例所监控的对象,这个对象可能是文件,也可能是目录。每个watch在内核中的表示如下:

struct inotify_watch {
	struct list_head	h_list;	/* entry in inotify_handle's list */
	struct list_head	i_list;	/* entry in inode's list */
	atomic_t		count;	/* reference count */
	struct inotify_handle	*ih;	/* associated inotify handle */
	struct inode		*inode;	/* associated inode */
	__s32			wd;	/* watch descriptor */
	__u32			mask;	/* event mask for this watch */
};

该结构中每个字段的含义如下:

h_list:一个inotify实例中所有watch组成一个双链表,h_list表示当前watch在该双链表中所处的结点。该双链表的表头即为inotify_handle结构中的watches字段。

i_list:一个文件可能被多个inotify实例监控,而被监控一次就产生一个watch,该文件对应的所有watch组成一个双链表,i_list表示当前watch在该双链表中所处的结点。该双链表的表头即为inode结构中的inotify_watches字段。

count:表示该watch被引用的次数。

ih:每个watch必然属于某个inotify实例,该字段指向当前watch所属的inotify_handle结构。

inode:该字段指向当前watch所关联的文件的inode。

wd:表示当前watch的文件描述符。

mask:表示当前监控对象所对应的事件掩码。

当初始化完一个inotify实例后,通过inotify_add_watch()即可向该实例中添加watch。不过在添加前还需通过inotify_init_watch()对每个watch进行初始化。

对watch初始化完成的主要工作即初始化两个链表结点、该watch的引用计数等。向inotify实例添加一个watch主要完成的工作即为向两个watch链表中添加当前的watch结点,并且更新watch的引用计数等。

3.删除一个watch

从指定的inotify实例中删除一个watch所完成的工作其实和添加watch的过程相反,通过inotify_rm_watch()即可完成。

4.对监控事件的处理

handle_event()通常并不对具体的监控对象做处理,而是作为所有监控事件的入口点;接着根据每个事件的掩码做出“分流”动作,即对具体的监控事件做以处理。

inotify机制在用户态下的使用方法

2011年9月24日

1.inotify是什么?

inotify机制用于监控文件系统,通过它可以监控一个或多个文件,如果该文件发生了指定事件,比如打开,读或写等,该机制会异步的响应用程序发出通知(或称为警告),应用程序根据文件系统发生的事件类型做出相应的反应。

2.inotify可以监控的事件

inotify使用一组宏来表示文件可以被监控的事件,这些宏在稍候介绍的inotify_add_watch()中使用。在没有特别说明的情况下,下面解释中的文件均指被监控的文件,并且即可以是普通文件又可以是目录文件。

IN_ACCESS:文件被访问,如果是目录文件,则指目录中的文件名被访问。

IN_MODIFY:文件被修改,如果是目录文件,则指目录中的文件名被修改。

IN_ATTRIB:文件属性被修改,比如使用chmod命令。

IN_CLOSE_WRITE:可写的文件被关闭。

IN_CLOSE_NOWRITE:不可写文件被关闭。

IN_CLOSE:文件被关闭,它等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)的效果。

IN_OPEN:文件被打开。

IN_MOVED_FROM:文件被移出监控区,比如使用mv命令将一个文件移出监控目录。

IN_MOVED_TO:文件(这个文件既可以是受监控的又可以是未受监控的)被移入监控区,比如使用mv和cp命令。

IN_MOVE:文件被移动,它等同于(IN_MOVED_FROM | IN_MOVED_TO)的作用效果。

IN_CREATE:在目录中创建一个新文件,比如touch或mkdir命令。

IN_DELETE:文件被删除,比如使用rm命令。

IN_DELETE_SELF:自删除,即一个可执行文件在执行时删除自己。

IN_MOVE_SELF:自移动,即一个可执行文件在执行时移动自己。

IN_UNMOUNT:宿主文件系统被 umount。

另外,IN_ISDIR宏用来判断被监控的文件是否为目录文件,该宏可以在应用程序对监控文件作监控处理时应用。

3.inotify用户态使用概述

inotify机制属于Linux在2.6.13之后增加的一个新特性,它属于dnotify机制的升级版。要使用inotify机制监控文件系统,那么必须先创建一个inotify的实例。由于Linux中的一切皆为文件,可以将inotify实例理解为一个“inotify类型的文件”,因此该实例会对应一个文件描述符,这也属于inotify优于dnotify的一大特性。

inotify机制的另一大特性即为监控程序对文件的监控不必轮询去查看,一旦监控的文件有指定的事件发生,它会异步通知监控程序,监控程序收到警告后会立马做出相应的响应。而在没有发生监控事件的时候,监控程序则一直处于阻塞状态。

这里的阻塞通过read()即可完成。当没有监控时间发生时,inotify实例中没有数据则read()阻塞;当有监控事件发生时,监控事件将被写入inotify实例中,此时read函数被唤醒读取该事件,监控程序根据读取的数据做出相应处理。这里的事件其实是通过字节流发送到inotify实例中的,因此可以通过read()函数来读取。为此,专门有一个数据结构来存储监控事件,即为struct inotify_event:

 struct inotify_event
struct inotify_event {
	__s32		wd;		/* watch descriptor */
	__u32		mask;		/* watch mask */
	__u32		cookie;		/* cookie to synchronize two events */
	__u32		len;		/* length (including nulls) of name */
	char		name[0];	/* stub for possible name */
}; 

该结构的定义位于用户态文件目录include/linux/inotify.h中,每个字段代表的含义如下:

wd:一个监视器(watch)的描述符,所谓监视器就是一个二元组(监视文件,事件掩码),其中事件掩码包含该文件被监视的所有事件。wd是通过inotify_add_watch()返回的,wd在此结构中与一个监视事件关联,即说明wd监视器上发生了当前inotify_event这个事件。

mask:该事件的类型即为当前结构中的mask,它是wd中所指定mask的一个子集。

len:表示当前结构中name的长度,但有时候name为了字节对齐会填充若干个0,因此len会大于等于name的长度。

name:表示监控文件的路径,这里通过GNU C中的0长度数组来表示变长的文件路径。

对inotify机制的典型使用方法如下:

1.创建并初始化一个inotify的实例,通过inotify_init()即可实现,该函数返回一个文件描述符。

2.添加一个或多个监控文件,即监视器,通过inotify_add_watch()即可实现,该函数就返回一个监视器的文件描述符。

3.循环等待监控事件的发生,通过循环read()inotify实例的fd即可实现。

4.如果有监控事件发生,则将fd中的字节流读取到inotify_event结构中,监控程序随之作适当处理,处理完毕后返回继续等待。

5.当不需要继续监控或收到某个代表监控结束的信号时,关闭inotify实例的文件描述符。

关于基本的使用流程还可以参考下图:

4.inotify用户态API

inotify的API都使用文件描述符,这样可以将监控粒度控制到单个文件,而dnotify机制的控制粒度则为单个目录。使用文件描述符更大的优势在于对inotify的操作也可以使用read()、close()、select()等这些传统的文件操作函数。

1.int inotify_init (void)

创建并初始化一个inotify实例,该函数返回一个文件描述符。可以认为这个函数是打开一个inotify类型的文件并返回该类型文件的描述符。

2.int inotify_add_watch (int __fd, const char *__name, uint32_t __mask)

增加监视文件(监视器),fd用于指明该文件被添加于哪个inotify实例,name用于指名该文件的路径,mask则指明了该文件所有的监控事件。该函数调用成功后返回一个监视器的描述符。

3.int inotify_rm_watch (int __fd, int __wd)

从fd中删除一个监视器,wd指名具体的监视器。

关于上述函数的详细的使用方法以及错误返回值等内容可以参考man手册。

参考:

IBM Developer Works:http://www.ibm.com/developerworks/cn/linux/l-ubuntu-inotify/index.html

内核模块的实现

2011年5月29日

内核模块再学习之模块的实现

如果你对内核模块编程已经有了简单的认识,那么可以更进一步学习模块在内核中的实现。对于每个内核模块来说,系统都为其分配一块内存区,这块内存区包括:一个module结构、唯一表示模块名称的字符串和实现模块功能的代码。

module结构中包含众多字段,从多个方面对内核模块进行描述,这一点如同task_struct结构对一个进程的全面描述一样。我们接下来只关注几个与我们平时简单的内核模块编程相关的字段。

enum module_state state:显示内核模块的状态;该枚举类型包含三种模块状态:MODULE_STATE_COMING、MODULE_STATE_LIVE和MODULE_STATE_GOING。分别表示正在加载模块、模块已经加载并且可用、正在卸载模块。
struct list_head list:内核中的所有模块结构通过双链表组织在一起,该字段表示当前模块在双链表中的节点。
char name[MODULE_NAME_LEN]:模块的名称,每个模块的名称都是唯一的。
int (*init)(void):init是一个函数指针,它指向模块的加载函数。
void (*exit)(void):exit是一个函数指针,它指向模块的卸载函数。
unsigned int core_size:用于模块核心函数与数据结构的动态内存区大小。

通过上述的字段分析,我们知道内核中的模块是通过双链表进行组织的。因此,我们就可以通过双链表的操作函数来打印内核中每个模块的相关信息。不过,模块链表的头指针modules并未被导出,所以我们通过在符号表中查看modules在内存中的地址来对其进行操作(关于通过符号表得到内核中某个未导出符号的地址可查看这里的文章)。整个程序的关键代码参考如下。

#define modules 0xc0771264

static int __init print_module_init(void)
{
	struct module *p;
	struct list_head *module_head;
	struct list_head *pos;
	int i = 0;

	printk("print_module module is starting..\n");

	module_head = (struct list_head *)modules;
	list_for_each(pos, module_head) {
		p = list_entry(pos, struct module, list);
		printk("%3d %20s %10d\n", ++i, p->name, p->core_size);
	}

	return 0;
}

遍历的方法很简单,先得到模块链表的头指针module_head,再通过list_for_each函数依次遍历每个内核模块。我们在此打印的模块信息是模块的名称和模块的大小,其输出结果和lsmod命令的输出结果相同。如下所示。

[ 7405.518605] print_module module is starting..
[ 7405.518610]   1         print_module        632
[ 7405.518613]   2                hello        830
[ 7405.518616]   3          binfmt_misc       6587
[ 7405.518618]   4           vboxnetadp       6808
[ 7405.518621]   5           vboxnetflt      18753
[ 7405.518624]   6                  via      37920
[ 7405.518626]   7                  drm     162345
[ 7405.518629]   8              vboxdrv     229952
…………

完整代码可以在这里下载。

使用list_head建立双联表

2011年5月24日

内核中与链表有关的操作都集中在list.h文件中,该文件不仅定义了链表数据结构,而且包含大量对链表操作的函数。

下面通过一个内核模块简单说明内核双链表的用法。该内核模块的加载函数首先建立了一个具有N个节点的双链表,然后再遍历该链表;在该内核模块卸载函数中,依次将每个节点从双链表中删除,并释放每个节点所占用的内核空间的内存。

在具体说明该程序之前,有必要搞清楚两个概念:链表中的数据节点和链表节点。内核在list.h中定义的链表节点list_head称之为链表节点。该结构除了指向前后节点的指针外,并不包含任何数据变量。因此,在实际使用的过程中,我们必须将所需数据和链表节点封装在一个数据结构中,这个新的数据结构就是数据节点。比如,在示例程序中,我们的数据节点除了包含链表节点外,还包含一个数据成员num。

#define N 10
//链表节点
struct numlist {
	int num;//数据
	struct list_head list;//指向双联表前后节点的指针
};
struct numlist numhead;//头节点

在使用双联表的操作函数时也应该注意数据节点和链表节点之间的差异。所有与双联表有关的操作函数都是对链表节点的操作,也就是说我们应该对这些函数传入链表节点(或指针)而不是数据节点。

在加载函数中,我们通过INIT_LIST_HEAD宏初始化链表的头指针。通过查看该宏的定义可知,必须向其传入一个list_head结构的指针变量,因此我们将&numhead.list传入其中。

接着,通过循环分别创建N个数据节点,并在创建完每个节点后,将其加入整个双链表中。将节点加入双链表的过程是通过list_add_tail函数完成的,该函数将新节点加入到双链表的末尾。

static int __init doublelist_init(void)
{
	//初始化头节点
	struct numlist *listnode;//每次申请链表节点时所用的指针
	struct list_head *pos;
	struct numlist *p;
	int i;

	printk("doublelist is starting...\n");
	INIT_LIST_HEAD(&numhead.list);

	//建立N个节点,依次加入到链表当中
		for (i = 0; i < N; i++) {
		listnode = (struct numlist *)kmalloc(sizeof(struct numlist), GFP_KERNEL);
		listnode->num = i+1;
		list_add_tail(&listnode->list, &numhead.list);
		printk("Node %d has added to the doublelist...\n", i+1);
	}

加载函数创建完双链表后,紧接着又对该双链表进行了遍历操作。我们在上面说过双链表的操作函数是对链表节点list_head进行操作的,因此遍历宏list_for_each也是对链表节点的遍历。如果我们需要得到数据节点中每个成员的值,那么就必须获得当前数据节点的指针,也就是该节点的首地址。因此list_entry宏的作用就是通过当前正在遍历的链表节点的指针pos获得其所属数据节点的指针p。

这里特别说明一下list_entry宏的参数,pos所指向的list_head结构在numlist结构中的字段称为list。

	//遍历链表
	i = 1;
	list_for_each(pos, &numhead.list) {
		p = list_entry(pos, struct numlist, list);
		printk("Node %d's data:%d\n", i, p->num);
		i++;
	}

	return 0;
}

在卸载函数中将会依次删除链表中的节点。list_for_each_safe遍历宏是专门为删除链表结点而设计的,它在遍历当前节点对同时保存下一个节点的地址。因此在每次遍历的时候一方面使用list_del将当前链表节点从整个链表中删除,一方面使用kfree函数释放该数据节点所占的内存空间。

static void __exit doublelist_exit(void)
{
	struct list_head *pos, *n;
	struct numlist *p;
	int i;

	//依次删除N个节点
	i = 1;
	list_for_each_safe(pos, n, &numhead.list) {
		list_del(pos);//从双联表中删除当前节点
		p = list_entry(pos, struct numlist, list);//得到当前数据节点的首地址,即指针
		kfree(p);//释放该数据节点所占空间
		printk("Node %d has removed from the doublelist...\n", i++);
	}
	printk("doublelist is exiting..\n");
}

该实例程序的完整代码可在此下载。

对/proc文件系统进行读写操作

2011年5月19日

本博客之前的文章中多次涉及到/proc文件系统,下面的几条命令都在曾经的文章中出现过:

cat /proc/interrupts
cat /proc/devices
cat /proc/kallsyms | grep super_blocks

第一条命令用于查看系统内已注册的中断信息,包括中断号、已接受的手段请求和驱动器名称等;第二条命令用于查看系统内已注册的字符设备和块设备信息,包括设备号和设备名称;第三条命令用于在内核符号表中检索super_blocks符号的的地址,kallsyms文件包括内核中所有的标示符及其地址。

1.概述

proc即process的缩写,最初的proc文件系统只是存放进程的相关信息。但现在的/proc文件系统除此之外还包含系统的状态信息和配置信息。
通过ls命令就可以查看/proc文件系统所包含的内容。

edsionte@edsionte-desktop:/proc$ ls
1      1290   1469  1541  1627   19612  29    49    9          dri              mdstat          sys
10     13     1471  1544  1630   19613  3     5     908        driver           meminfo         sysrq-trigger
1013   1301   1474  1548  1632   19629  30    50    913        edsionte_procfs  misc            sysvipc
…………

其中以数字为名的目录即为系统中正在运行的进程信息,数字即为进程的pid。比如我们可以进入init进程的目录,查看它的地址空间:

edsionte@edsionte-desktop:/proc/1$ sudo cat maps
[sudo] password for edsionte:
00110000-00263000 r-xp 00000000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00263000-00264000 ---p 00153000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00264000-00266000 r--p 00153000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00266000-00267000 rw-p 00155000 08:07 704702     /lib/tls/i686/cmov/libc-2.11.1.so
00267000-0026a000 rw-p 00000000 00:00 0
0026a000-00272000 r-xp 00000000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00272000-00273000 r--p 00007000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00273000-00274000 rw-p 00008000 08:07 704713     /lib/tls/i686/cmov/libnss_nis-2.11.1.so
00471000-0048b000 r-xp 00000000 08:07 1048610    /sbin/init
…………

除了查看进程的相关信息,我们还可以通过打印相关文件来查看系统的当前运行状态。比如查看当前内存的使用情况:

edsionte@edsionte-desktop:/proc$ cat meminfo
MemTotal:         961368 kB
MemFree:          145264 kB
Buffers:           31648 kB
Cached:           297716 kB
SwapCached:        14436 kB
…………

总之,/proc文件系统相当于内核的一个快照,该目录下的所有信息都是动态的从正在运行的内核中读取。

基于这种原因,/proc文件系统就成为了用户和内核之间交互的接口。一方面,用户可以从/proc文件系统中读取很多内核释放出来的信息;另一方面,内核也可以在恰当的时候从用户那里得到输入信息,从而改变内核的相关状态和配置。

相比传统的文件系统,/proc是一种特殊的文件系统,即虚拟文件系统。这里的虚拟是强调/proc文件系统下的所有文件都存在于内存中而不是磁盘上。也就是说/proc文件系统只占用内存空间,而不占用系统的外存空间。

2.用户态和内核态之间的数据通信

既然内核的数据以/proc文件系统的形式呈现给用户,也就是说内核的信息以文件的形式存在于该文件系统中,那么/proc文件系统就应当提供一组接口对其内的文件进行读写操作。接下来我们以一个实际的内核模块程序easyProc.c为例,说明/proc文件系统的常用接口。该程序中依次创建了几个虚拟文件,然后在用户态对这些文件进行读写测试。

2.0数据结构

每个虚拟文件都对应一个proc_dir_entry类型的数据结构,该结构具体定义如下:

struct proc_dir_entry {
	const char *name;			// virtual file name
	mode_t mode;				// mode permissions
	uid_t uid;				// File's user id
	gid_t gid;				// File's group id
	struct inode_operations *proc_iops;	// Inode operations functions
	struct file_operations *proc_fops;	// File operations functions
	struct proc_dir_entry *parent;		// Parent directory
	...
	read_proc_t *read_proc;			// /proc read function
	write_proc_t *write_proc;		// /proc write function
	void *data;				// Pointer to private data
	atomic_t count;				// use count
	...
};

除了保存该虚拟文件的基本信息外,该结构中还有read_proc和write_proc两个字段,下文中将有详细说明。

2.1创建目录

/proc文件系统中创建一个目录对应的函数接口如下:
struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent);
其中name为要创建的目录名;parent为这个目录的父目录,当要创建的目录位于/proc下时此参数为空。比如我们使用该函数在/proc下创建一个目录edsionte_procfs。

#define MODULE_NAME "edsionte_procfs"
struct proc_dir_entry *example_dir;
	example_dir = proc_mkdir(MODULE_NAME, NULL);
	if (example_dir == NULL) {
		rv = -ENOMEM;
		goto out;
	}
2.2创建普通文件

在/proc文件系统中创建一个虚拟文件可以使用如下的函数:

 static inline struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent) ;

该函数中name为要创建的文件名;mode为创建文件的属性;parent指向该文件父目录的指针,如果创建的虚拟文件位于/proc下,则这个参数为NULL。

比如我们通过该函数在/proc/edsionte_procfs目录下创建一个虚拟文件foo,其权限为644。其中example_dir指向我们刚创建的目录文件edsionte_procfs。

struct proc_dir_entry  *foo_file;
	foo_file = create_proc_entry("foo", 0644, example_dir);
	if (foo_file == NULL) {
		rv = -ENOMEM;
		goto no_foo;
	}
2.3.创建符号链接文件

当我们需要在/proc文件系统下创建一个符号链接文件时,可使用如下接口:

struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest);

name参数为要创建的符号链接文件名;parent为该符号链接文件的父目录;dest为符号链接所指向的目标文件。

下面的代码演示了如何通过该函数来对已存在的虚拟文件jiffies创建符号链接文件jiffies_too:

	symlink = proc_symlink("jiffies_too", example_dir, "jiffies");
	if (symlink == NULL) {
		rv = -ENOMEM;
		goto no_symlink;
	}

我们内核模块加载函数中完成上述几个虚拟文件的创建工作。

2.4.删除文件或目录

既然有创建虚拟文件的函数,必然也就有删除虚拟文件的函数接口:

void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

该函数中的参数name和parent与上述函数的参数意义相同。
在示例程序中,我们在卸载函数中完成上述几个文件的删除工作:

	remove_proc_entry("jiffies_too", example_dir);
	remove_proc_entry("foo", example_dir);
	remove_proc_entry("MODULE_NAME", NULL);
2.5读写proc文件

如果只是创建了虚拟文件,那么它并不能被读写。为此,我们必须为每个虚拟文件挂接读写函数,如果该虚拟文件是只读的,那么只需挂载相应的读函数。

正如上面所述,每个虚拟文件对应的proc_dir_entry结构都有read_proc和write_proc两个字段,它们均为函数指针,其各自的类型定义如下:

 typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);
  typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);

如果要实现对虚拟文件的读写,则需要实现上述两个函数接口。对于我们的示例程序,我们的实现方法如下:

static int proc_read_foobar(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int len;
	struct fb_data_t *fb_data = (struct fb_data_t *)data;

	//将fb_data的数据写入page
	len = sprintf(page, "%s = %s\n", fb_data->name, fb_data->value);

	return len;
}

static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data)
{
	int len;
	struct fb_data_t *fb_data = (struct fb_data_t *)data;

	if (count > FOOBAR_LEN)
		len = FOOBAR_LEN;
	else
		len = count;

	//写函数的核心语句,将用户态的buffer写入内核态的value中
	if (copy_from_user(fb_data->value, buffer, len))
		return -EFAULT;

	fb_data->value[len] = '\0';

	return len;
}

当用户读我们刚创建的虚拟文件时,该文件对应的read_proc函数将被调用。该函数将数据写入内核的缓冲区中。上述读函数的例子中,缓冲区即为page。当用户给虚拟文件写数据时,write_proc函数将被调用,该函数从缓冲区buffer中读取count个字节的数据。

3.测试

接下来我们将进行一系列的读写测试。由于我们只为jiffies与其符号链接文件jiffies_too实现了读回调函数,因此它们为只读文件,当对这两个文件进行写操作时就会出现错误;对于foo和bar文件,我们为其实现了读、写函数,因此既可以对它们进行读操作也可以进行写操作。

root@edsionte-desktop:/proc/edsionte_procfs# cat jiffies
jiffies = 833619
root@edsionte-desktop:/proc/edsionte_procfs# cat jiffies_too
jiffies = 834442
root@edsionte-desktop:/proc/edsionte_procfs# cat bar
bar = bar
root@edsionte-desktop:/proc/edsionte_procfs# cat foo
foo = foo
root@edsionte-desktop:/proc/edsionte_procfs# echo "time" > jiffies
bash: echo: 写操作出错: 输入/输出错误
root@edsionte-desktop:/proc/edsionte_procfs# echo "time" > jiffies_too
bash: echo: 写操作出错: 输入/输出错误
root@edsionte-desktop:/proc/edsionte_procfs# echo "hello" >> bar
root@edsionte-desktop:/proc/edsionte_procfs# cat bar
bar = hello

示例程序可以在此下载完成代码。

参考:

1.边干边学-Linux内核指导;作者: 李善平;出版社: 浙江大学出版社;

2.使用 /proc 文件系统来访问 Linux 内核的内容;
http://www.ibm.com/developerworks/cn/linux/l-proc.html

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key