目录

内核空间和用户空间通信 - proc 虚拟文件系统

概述

使用 proc 虚拟文件系统来在内核空间和用户空间交换数据应该是最常用的一种方式了。我们可以在 /proc 目录下创建一个虚拟的文件,通过读写这个文件实现用户空间和内核空间的数据交互。

需要说明的是,虽然这里我们只讨论 procfs,但是在在新一些的 linux kernel 中我们应该使用 sysfs 来替代 procfs 作为 driver 和 user space 的接口。

代码示例

废话不多说,先上例子。(例子是基于 linux 3.9.9 测试的,但是理论上适用于到目前为止的所有 kernel 版本)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
 * procfs1.c
 */

#include <linux/kernel.h>		/* We're doing kernel work */
#include <linux/module.h>		/* Specifically, a module */
#include <linux/proc_fs.h>		/* Necessary because we use the proc fs */
#include <linux/uaccess.h>		/* for copy_from_user */
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
#define HAVE_PROC_READ_SUITE
#endif

#define PROCFS_MAX_SIZE		1024
#define PROCFS_NAME			"pbuf"

/* This structure hold information about the /proc file */
static struct proc_dir_entry *our_proc_file;

/* The buffer used to store character for this module */
static char procfs_buffer[PROCFS_MAX_SIZE];
/* The size of the buffer */
static unsigned long procfs_buffer_size = 0;

static ssize_t procfile_read(struct file *file, const char __user *buff,
                             size_t len, loff_t *off)
{
	int plen = procfs_buffer_size;
    ssize_t ret = plen;

    if (*off >= plen || copy_to_user(buff, procfs_buffer, plen)) {
        pr_info("copy_to_user failed\n");
        ret = 0;
    } else {
        pr_info("procfile read %s\n", file->f_path.dentry->d_name.name);
        *off += plen;
    }

    return ret;
}

/*
cat /dev/zero | tr "\0" "a" | dd of=/tmp/1.txt bs=1023 count=1
cat /tmp/1.txt > /proc/pbuf
*/
static ssize_t procfile_write(struct file *file, const char __user *buff,
                              size_t len, loff_t *off)
{
    procfs_buffer_size = len;
    if (procfs_buffer_size > PROCFS_MAX_SIZE)
        procfs_buffer_size = PROCFS_MAX_SIZE;

	if (copy_from_user(procfs_buffer, buff, procfs_buffer_size)) {
		pr_info("copy_from_user failed\n");
        return -EFAULT;
	}

	/* supports only max to 1023 bytes string */
	procfs_buffer[procfs_buffer_size & (PROCFS_MAX_SIZE - 1)] = '\0';
    pr_info("procfile write %s\n", procfs_buffer);

    return procfs_buffer_size;
}

#ifdef HAVE_PROC_OPS
static const struct proc_ops proc_file_fops = {
    .proc_read = procfile_read,
	.proc_write = procfile_write,
};
#else
static const struct file_operations proc_file_fops = {
    .read = procfile_read,
	.write = procfile_write,
};
#endif

static int __init procfs1_init(void)
{
    our_proc_file = proc_create(PROCFS_NAME, 0644, NULL, &proc_file_fops);
    if (NULL == our_proc_file) {
#ifdef HAVE_PROC_READ_SUITE
		remove_proc_entry(PROCFS_NAME, NULL);
#else
        proc_remove(our_proc_file);
#endif
        pr_alert("Error:Could not initialize /proc/%s\n", PROCFS_NAME);
        return -ENOMEM;
    }

    pr_info("/proc/%s created\n", PROCFS_NAME);
    return 0;
}

static void __exit procfs1_exit(void)
{
#ifdef HAVE_PROC_READ_SUITE
		remove_proc_entry(PROCFS_NAME, NULL);
#else
        proc_remove(our_proc_file);
#endif
    pr_info("/proc/%s removed\n", PROCFS_NAME);
}

module_init(procfs1_init);
module_exit(procfs1_exit);

MODULE_LICENSE("GPL");

为了测试方便 Makefile 也随手贴一下,注意路径自己定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
KERNEL_DIR=
ARCH=
CROSS_COMPILE=

obj-m += procfs1.o
ccflags-y := -g -DDEBUG

.PHONY: all clean

PWD := $(CURDIR)

all:
        $(MAKE) -C '$(KERNEL_DIR)' M='$(PWD)' ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) V=1 modules

clean:
        $(MAKE) -C '$(KERNEL_DIR)' M='$(PWD)' clean

编译后会生成模块文件 procfs1.ko 。在对应的 linux guest 机器上测试

1
2
3
4
5
6
7
8
9
$ insmod procfs1.ko
$ echo 'HelloWorld!' > /proc/pbuf
procfile write HelloWorld!

$ cat /proc/pbuf
procfile read pbuf
HelloWorld!
copy_to_user failed
copy_to_user failed

使用说明

关键接口函数和结构

从上例可以看出,创建一个 proc 虚拟文件包含了以下的几个步骤

  • 定义一个 struct proc_ops 或者 struct file_operations 的实例,read 和 write 的函数指向自定义的 procfile_readprocfile_write 回调函数

  • 调用函数 proc_create 创建一个虚拟文件,并将参数 proc_fops 对应的 struct file_operations 实例与之关联

  • 实现读写的回调函数 procfile_readprocfile_write

  • 在模块退出时,调用函数 remove_proc_entry 或者 proc_remove 将 proc 虚拟文件删除

对应的几个函数都可以在头文件 include/linux/proc_fs.h 中找到声明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
				struct proc_dir_entry *parent,
				const struct file_operations *proc_fops,
				void *data);
static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode,
	struct proc_dir_entry *parent, const struct file_operations *proc_fops)
{
	return proc_create_data(name, mode, parent, proc_fops, NULL);
}
extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

主要的数据结构则是 struct proc_dir_entry

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct proc_dir_entry {
	// ...
	const struct inode_operations *proc_iops;
    const struct file_operations *proc_dir_ops;

    // ...
};

struct file_operations {
    // ...
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    // ...
};

从上面的分析可以知道,最重要的就是 procfile_readprocfile_write 两个回调函数了。write 函数的参数说明如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*

procfile_write函数将数据从用户空间的buff 中拷贝到文件的 (*off) 位置处。

file: [IN] 文件指针

buff: [IN] 需要写入文件的数据起始指针。注意这个buff是在user space的,所以需要使
      用copy_from_user 拷贝到内核空间。同时需要注意 copy_from_user 函数返回的是
      还没有被拷贝到用户空间去的数据长度,而不是像一般的返回拷贝成功的长度。

len: [IN] 需要写入的数据长度

off: [IN/OUT] 表示文件的偏移地址offset。数据应该从 buff 的位置开始写入到文件的
     (*off) 偏移出。每一次被调用后,函数需要根据写入的数据长度调整 (*off) 的值。

return: 成功写入的数据长度
*/
static ssize_t procfile_write(struct file *file,
							  const char __user *buff,
                              size_t len,
							  loff_t *off)

/*
procfile_read 函数负责从文件的(*off)位置拷贝数据到用户空间的 buff 中去。

file: [IN] 文件指针

buff: [OUT] 需要读出的数据的存放位置。注意这个buff是在user space的,所以需要使用
      copy_to_user 拷贝到用户空间。同时需要注意 copy_to_user 函数返回的是还没有
      被拷贝到用户空间去的数据长度,而不是像一般的返回拷贝成功的长度。

len: [IN] 需要读取的数据长度

off: [IN/OUT] 表示文件的偏移地址offset。数据应该从文件的(*off) 偏移处读取到 buff
     中。每一次被调用后,函数需要根据已经读取的数据长度调整 (*off) 的值。

return: 成功读取的数据长度
*/
static ssize_t procfile_read(struct file *file,
							 const char __user *buff,
                             size_t len,
							 loff_t *off)

这两个函数的具体说明可以参考 Linux Device Drivers, Third Edition 的第三章讲解的 read 和 write 函数部分,大同小异。

深入 procfile_read

了解了接口函数的使用,我们可以回到先前的例子再仔细分析一下输出。可以看到在 cat /proc/pbuf 的时候打印了两次的 “copy_to_user failed”。为什么会打印两次呢?

因为 cat 默认是使用 sendfile 函数来直接在 kernel space 中从 /proc/pbuf 文件传递数据到 stdout 的。只有当 sendfile 返回值为 0(eof)的时候 cat 才会退出。

对于 /proc/pbuf 文件的读回调函数来说,其调用栈如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
1  procfile_read            procfs1.c    30   0xc87e602a
2  proc_reg_read            inode.c      197  0xc10c82d8
3  do_loop_readv_writev     read_write.c 636  0xc108d7a9
4  do_readv_writev          read_write.c 768  0xc108d9ac
5  vfs_readv                read_write.c 790  0xc108d9eb
6  kernel_readv             splice.c     567  0xc10ab12d
7  default_file_splice_read splice.c     643  0xc10ac9e6
8  do_splice_to             splice.c     1146 0xc10ab496
9  splice_direct_to_actor   splice.c     1217 0xc10ab561
10 do_splice_direct         splice.c     1307 0xc10acce2
11 do_sendfile              read_write.c 968  0xc108ddff
12 sys_sendfile64           read_write.c 1023 0xc108dfde
13 ia32_sysenter_target     entry_32.S   439  0xc12733be
14 ??                                         0xffffe424
15 ??                                         0x804fd47
16 ??                                         0x80e9616

由于函数 splice_direct_to_actor 中会循环调用 do_splice_to 函数来传递数据,直到返回值为 0 (也就是 /proc/pbuf 的读回调函数返回 0)。所以其实 do_sendfile 被调用了 2 次,而 procfile_read 则被调用了 3 次。过程如下所示:

1
2
3
4
5
- do_sendfile (return 13)
  - procfile_read (return 13)
  - procfile_read (return 0)
- do_sendfile (return 0)
  - procfile_read (return 0)

接口函数的演变

为什么在上面的示例代码里那么多的宏?这是因为随着 linux kernel 的不断更新,proc 虚拟文件的使用方式也发生了多次变化。最重要的变化如下表所示(参见头文件 include/linux/proc_fs.h

VERSION CHANGE
v3.10 remove read_proc and write_proc from struct proc_dir_entry
v5.6 use ‘struct proc_ops’ to instead of ‘struct file_operation’

翻看 3.10 以前的 linux kernel,可以看到 proc 文件系统的核心数据结构 proc_dir_entry 如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct proc_dir_entry {
    // ...
	const struct inode_operations *proc_iops; // Inode operations functions
	const struct file_operations *proc_fops;  // File operations functions
    // ...
	read_proc_t *read_proc;                   // proc read function
	write_proc_t *write_proc;                 // proc write function
    // ...
};

struct file_operations {
    // ...
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    // ...
};

也就是说读写文件可以通过两种方式进行

  • A. 通过调用 proc 提供的函数指针接口 read_procwrite_proc
  • B. 通过标准的文件系统接口 proc_fops->readproc_fops->write

再看下 5.6 的数据结构( fs/proc/internal.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct proc_dir_entry {
	// ...
	const struct inode_operations *proc_iops;
	union {
		const struct proc_ops *proc_ops;
		const struct file_operations *proc_dir_ops;
	};
    // ...
};

struct proc_ops {
	// ...
	ssize_t	(*proc_read)(struct file *, char __user *, size_t, loff_t *);
	ssize_t	(*proc_write)(struct file *, const char __user *, size_t, loff_t *);
    // ...
} __randomize_layout;

可以发现, read_procwrite_proc 接口被移除了;同时多了一个 struct proc_ops 结构指针。之所以把原来的 struct file_operations 替换为 struct proc_ops ,原因是 struct file_operations 包含了很多 VFS 不需要的成员;替换成 struct proc_ops 作为 proc VFS 专用的结构体,可以避免在 struct file_operations 增加新功能的时候影响 proc VFS,也可以通过在 struct proc_ops 中添加特定的一些成员来扩展 proc VFS 的功能。

由于 proc_ops->proc_readproc_ops->proc_write 函数原型和 file_operations->readfile_operations->write 完全一样。所以最佳使用方式应该是通过文件系统来读取 proc 的虚拟文件。这样不管是 5.6 之前的 file_operations 或者之后的 proc_ops 都可以使用一样的 read write 函数了。

高级应用

数据量大于 PAGE_SIZE

如果仔细阅读上面的代码就会注意到,这个例子中的 readfile_proc 只能支持数据量小于等于一个 PAGE_SIZE 的情形。如果需要读取大于一个 PAGE_SIZE 的数据,那么函数应该怎么写呢?示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#define PROCFS_MAX_SIZE		(1024*8)

/* The buffer used to store character for this module */
static char procfs_buffer[PROCFS_MAX_SIZE];
/* The size of the buffer */
static unsigned long procfs_buffer_size = 0;

static ssize_t procfile_read(struct file *file, const char __user *buff,
                             size_t len, loff_t *off)
{
	size_t count = len, data_length = sizeof(procfs_buffer);
    ssize_t retval = 0;
    unsigned long ret = 0;

    if (*off >= data_length)
        goto out;
    if (*off + len > data_length)
        count = data_length - *off;

    /* ret contains the amount of chars wasn't successfully written to `buf` */
    ret = copy_to_user(buff, procfs_buffer + *off, count);
    *off += count - ret;
    retval = count - ret;

out:
    return retval;
}

/*
cat /dev/zero | tr "\0" "a" | dd of=/tmp/1.txt bs=1023 count=1
cat /tmp/1.txt > /proc/pbuf
*/
static ssize_t procfile_write(struct file *file, const char __user *buff,
                              size_t len, loff_t *off)
{
	unsigned long write_size = len;
	unsigned long left_size = PROCFS_MAX_SIZE - procfs_buffer_size;

    if (write_size > left_size)
        write_size = left_size;

	if (copy_from_user(procfs_buffer + procfs_buffer_size, buff, write_size)) {
		pr_info("copy_from_user failed\n");
        return -EFAULT;
	}

	pr_info("procfile write offset %lu with length %lu\n", procfs_buffer_size, write_size);
	procfs_buffer_size += write_size;
	*off += write_size;

    return write_size;
}

proc 专用接口

如果是使用的是 3.10 以后的内核版本,那么请出门右转,因为下面的内容对你帮助不大。新的内核已经不支持这种使用方式。但是如果你使用的还是旧版的 kernel,那么请仔细的往下看。因为在网络上搜一下就会发现,很少有文档会去详细解释这个接口怎么使用。或者解释了也是云里雾里,很难看懂。

在看我写的文字之前,建议先学习一下 kernel 自带的文档和例程,毕竟这才是最权威的

然后如果还不明白,可以参考我下面的补充说明。

实例说明

还是老规矩,先举例子,如果你只是想用一下,不想知道太多(很忙),那么照例子写就完了。由于 proc 的专用接口提供了几种写法,所以需要分情况来举例。(就很烦)

由于 write 回调函数相对比较清楚,我只对 read 函数进行分析。

传输数据量小于等于一个 PAGE_SIZE

在早期的 linux kernel 中,有大量的使用 proc 专用接口来读写 proc 虚拟文件的例子。主要有以下的两种

  • 写法 A
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
static int ap_debug_proc_read(char *page, char **start, off_t off,
                              int count, int *eof, void *data)
{
    char *p = page;
    struct ap_data *ap = (struct ap_data *) data;

    if (off != 0) {
        *eof = 1;
        return 0;
    }

    p += sprintf(p, "BridgedUnicastFrames=%u\n", ap->bridged_unicast);
    p += sprintf(p, "BridgedMulticastFrames=%u\n", ap->bridged_multicast);

    return (p - page);
}
  • 写法 B
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static int ap_debug_proc_read(char *page, char **start, off_t off,
                              int count, int *eof, void *data)
{
    char *p = page;
    struct ap_data *ap = (struct ap_data *) data;

    p += sprintf(p, "BridgedUnicastFrames=%u\n", ap->bridged_unicast);
    p += sprintf(p, "BridgedMulticastFrames=%u\n", ap->bridged_multicast);

	*eof = 1;
    return (p - page);
}

如果查看调用这个回调函数的函数 __proc_file_read (实现在文件 fs/proc/generic.c 中)就可以发现,B 的写法要更好一些,A 则会多循环一次。

这种写法其实也就是在函数 __proc_file_read 注释中提到的 3 种做法中的第一种。原文摘录如下:

1
2
3
4
5
6
7
Leave *start = NULL.  (This is the default.) Put the data of the requested
offset at that offset within the buffer.  Return the number (n) of bytes there
are from the beginning of the buffer up to the last byte of data.  If the number
of supplied bytes (= n - offset) is greater than zero and you didn't signal eof
and the reader is prepared to take more data you will be called again with the
requested offset advanced by the number of bytes absorbed.  This interface is
useful for files no larger than the buffer.

还不理解是不是?换种说法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
1st call:
- *start = NULL
# enter proc_read
- memcpy(page + offset, internal_data + offset, data_copied_to_page_len)
- data_copied_to_user_len = data_copied_to_page_len
- return (offset + data_copied_to_user_len)
# exit proc_read
- copy data of data_copied_to_user_len from kernel space (page + offset) to user
  space (buf + offset)
- offset += data_copied_to_user_len

2nd call:
- *start = NULL
# enter proc_read
- memcpy(page + offset, internal_data + offset, data_copied_to_page_len)
- data_copied_to_user_len = data_copied_to_page_len
- return (offset + data_copied_to_user_len)
# exit proc_read
- copy data of data_copied_to_user_len from kernel space (page + offset) to user
  space (buf + offset)
- offset += data_copied_to_user_len

在这种情况下,start 不被使用。需要关注的只有 offset 和 eof 。

从前面的函数调用可以看出,虽然一般用 eof 来判断是否读取结束,但是即使没有设置 eof 也能结束读取循环。只要把数据读取结束即可。只是这样容易出错且效率低,所以显式的指定 eof 是个更好的编程习惯。

如果对于 offset 和 eof 的理解还是不够清楚,可以参考 stackoverflow 上的一段举例,我觉得还是挺清楚的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
off is the position in the file from where data has to be read from. This is
like off set of normal file. But, in the case of proc_read it is some what
different. For example if you invoke a read call on the proc file to read 100
bytes of data, off and count in the proc_read will be like this:

in the first time, off = 0, count 100. Say for example in your proc_read you
have returned only 10 bytes. Then the control cannot come back to the user
application, your proc_read will be called by the kernel once again with off as
10 and count as 90. Again if you return 20 in the proc_read, you will again
called with off 30, count 70. Like this you will be called till count reaches
0. Then the data is written into the given user buffer and your application
read() call returns.

But if you don't have hundred bytes of data and want to return only a few bytes,
you must set the eof to 1. Then the read() function returns immediately.

传输数据量大于一个 PAGE_SIZE

还是先看例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define BUFSIZ 8192
static char output_data[BUFSIZ] = {0};

static int read_method2(char *page,
						char **start,
						off_t offset,
						int count,
						int *eof,
						void *data)
{
    int len = strlen(output_data);
	int read_size = PAGE_SIZE-1; // suppose PAGE_SIZE is 4K

    if (offset >= len) {
        *eof = 1;
		return 0;
	}
    memcpy(page, output_data+offset, read_size);
	*start = read_size;

    return (read_size);
}

所以如果 caller 请求读取的是 8K 字节,那么第一次调用 read_proc 的时候 offset 0, count 8192;第二次 offset = offset + (*start) ,即 4095 (4K-1),count 4097;第三次 offset 8190,count 2 …

这里使用的是 3 种方式中的第二种做法。原文如下

1
2
3
4
5
6
7
1) Set *start = an unsigned long value less than the buffer address but greater
than zero. Put the data of the requested offset at the beginning of the buffer.
Return the number of bytes of data placed there.  If this number is greater than
zero and you didn't signal eof and the reader is prepared to take more data you
will be called again with the requested offset advanced by *start. This
interface is useful when you have a large file consisting of a series of blocks
which you want to count and return as wholes.

这个比较难理解一些。因为首先 *start 需要设置成一个 unsigned long 的值,并且要大于 0 小于 page 的地址(比如 0x80002000)。在这种情况下,offset 会根据 *start 的值进行递增。

首先需要注意第一次调用 read_proc 的时候 *start 必定是 NULL。这是函数 __proc_file_read 里面写死的。具体一些,上面这段注释可以按以下逻辑理解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
1st call:
- *start = NULL
# enter proc_read
- memcpy(page, internal_data + offset, data_copied_to_page_len)
- *start = N (0 < *start < page)
- data_copied_to_user_len = data_copied_to_page_len
- return data_copied_to_user_len
# exit proc_read
- copy data of data_copied_to_user_len from kernel space(page) to user space
- offset += *start

2nd call:
- *start >= page
# enter proc_read
- memcpy(page, internal_data + offset, data_copied_to_page_len)
- *start = N (0 < *start < page)
- data_copied_to_user_len = data_copied_to_page_len
- return data_copied_to_user_len
# exit proc_read
- copy data of data_copied_to_user_len from kernel space(page) to user space
- offset += *start

这里其实有个隐藏的条件。由于每次 offset 是根据 *start 递增,而用户空间的 buf 却是根据返回值(n)递增,所以 n 应该和 *start 相等。又 buf 是从 page 中的数据拷贝而来,所以拷贝的数据长度(data_copied_to_user_len,data_copied_to_page_len)应该和 *start 相等才合理。

这里有个问题需要注意。由于每次都是按照 *start 的长度来拷贝 page 中的内容到 user space 去的,那么如果最后一次 read_proc 被调用的时候 *start 被设置成了一个超出剩余长度的值,那么就会从 page 中拷贝多余的信息到用户空间。另外,由于 __proc_file_read 中的控制循环的变量 nbytes 是一个无符号型的数,所以永远不会 < 0 ,只有在 = 0 的时候才可能退出。因此在这种情况下,如果没有用 *eof 显式的指定结束而退出,那么有可能就进入一个死循环了。所以 *start 的值应该根据当前剩余数据的长度进行调整。

这种方式和第一种最大的区别在于返回值。当 start == NULL 的时候返回值应该是 page 中所有数据的长度(offset + 拷贝的长度),而当 start > 0 && start < (unsigned long)page 的时候,返回值只是拷贝的数据长度。

另外一种方式就是函数 __proc_file_read 注释中提到的最后一种了。这种方式也可以处理数据量大于一个 PAGE_SIZE 的情形。

示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static int hp_sdc_rtc_read_proc(char *page, char **start, off_t off,
								int count, int *eof, void *data)
{
    int len = hp_sdc_rtc_proc_output (page);
    if (len <= off+count) *eof = 1;
    *start = page + off;
    len -= off;
    if (len>count) len = count;
    if (len<0) len = 0;
    return len;
}

原文摘录如下:

1
2
3
4
5
2) Set *start = an address within the buffer. Put the data of the requested
offset at *start. Return the number of bytes of data placed there. If this
number is greater than zero and you didn't signal eof and the reader is prepared
to take more data you will be called again with the requested offset advanced by
the number of bytes absorbed.

首先需要注意第一次调用 read_proc 的时候 start 必定是 NULL。这是函数~__proc_file_read~ 里面写死的。所以上面这段注释所对应的行为必然是从第二次进入才会发生。我们可以这么理解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
1st call:
- *start = NULL
# enter proc_read
- memcpy(page, internal_data, data_copied_to_page_len)
- *start = page + offset (*start >= page)
- data_copied_to_user_len = data_copied_to_page_len - offset
- return data_copied_to_user_len
# exit proc_read
- copy data of data_copied_to_user_len from kernel space(*start) to user space
- offset += data_copied_to_user_len
# 注意read_proc 调用者的目的是从文件的offset处读取,所以当把数据放在page中时,需
# 要再加上offset赋值给 *start

2nd call:
- *start >= page
# enter proc_read
- memcpy(page, internal_data, data_copied_to_page_len)
- *start = page + offset (*start >= page)
- data_copied_to_user_len = data_copied_to_page_len - offset
- return data_copied_to_user_len
# exit proc_read
- copy data of data_copied_to_user_len from kernel space(*start) to user space

参考