转载_Linux procfs开发指南(部分翻译并做了精简)
2013-08-09 14:24
330 查看
Linux Kernel Procfs Guide
Chapter 1. Introduction
/proc文件系统(procfs)在linux内核中是一个特殊的文件系统,是一个虚拟的文件系统,只存在于内存中
注:proc/sys是sysctl文件,不属于procfs
Chapter 2. Managing procfs entries
要使用procfs,需包含#include <linux/proc_fs.h>
1.Creating a regular file
struct proc_dir_entry* create_proc_entry(const char* name,mode_t mode, struct proc_dir_entry* parent);
parent为NULL时,表示文件建立在/proc目录下,失败返回NULL
create_proc_read_entry:创建并初始化一个procfs入口in one single call
2. Creating a symlink
struct proc_dir_entry* proc_symlink(const char* name, struct proc_dir_entry* parent, const char* dest);
在parent目录下创建一个从name到dest的符号链接,像:ln -s dest name
3. Creating a device
struct proc_dir_entry* proc_mknod(const char* name, mode_t mode,struct proc_dir_entry* parent, kdev_t rdev);
创建一个名为name设备文件,mode参数必须包含S_IFBLK或者S_IFCHR,像:mknod --mode=mode name rdev
4. Creating a directory
struct proc_dir_entry* proc_mkdir(const char* name, struct proc_dir_entry* parent);
在parent目录下创建名为name的目录
5.Removing an entry
void remove_proc_entry(const char* name, struct proc_dir_entry* parent);
从procfs中移除parent目录下的name入口。注意它不会递归移除
Chapter 3. Communicating with userland
procfs对文件的读写并不是直接的,而是通过call back函数来操作read_proc and/or write_proc
在create_proc_entry函数返回一个proc_dir_entry* entry,然后设置
entry->read_proc = read_proc_foo;
entry->write_proc = write_proc_foo;
1. Reading data
kernel向用户返回数据
int read_func(char* page, char** start, off_t off, int count,int* eof, void* data);
从start处的off偏移处开始写最多count个字节的内容到page中。
2. Writing data
用户写数据到kernel
int write_func(struct file* file, const char* buffer, unsigned long count, void* data);
从buffer中读count字节,buffer不存在于kernel memory空间,所以需要使用copy_from_user函数,file常NULL
3.A single call back for many files
struct proc_dir_entry* entry;
struct my_file_data *file_data;
file_data = kmalloc(sizeof(struct my_file_data), GFP_KERNEL);
entry->data = file_data;
int foo_read_func(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len;
if(data == file_data) {
/* special case for this file */
} else {
/* normal processing */
}
return len;
}
在 remove_proc_entry是要free掉data
Chapter 4. Tips and tricks
1. Convenience functions
struct proc_dir_entry* create_proc_read_entry(const char* name,mode_t mode, struct proc_dir_entry* parent, read_proc_t* read_proc, void* data);
将只需要读操作的create_proc_entry简化掉了
2. Modules
struct proc_dir_entry* entry;
entry->owner = THIS_MODULE;
目前发现没有这个owner参数了,不用关注。
3. Mode and ownership
设置模式和权限
struct proc_dir_entry* entry;
entry->mode = S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH;
entry->uid = 0;
entry->gid = 100;
Chapter 5. Example
board (http://www.lart.tudelft.nl/), which is
* sponsored by the Mobile Multi-media Communications
* (http://www.mmc.tudelft.nl/) and Ubiquitous Communications
* (http://www.ubicom.tudelft.nl/) projects.
*
* The author can be reached at:
*
* Erik Mouw
* Information and Communication Theory Group
* Faculty of Information Technology and Systems
* Delft University of Technology
* P.O. Box 5031
* 2600 GA Delft
* The Netherlands
*
*
* This program is free software; you can redistribute
* it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place,* Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define MODULE_VERSION "1.0"
#define MODULE_NAME "procfs_example"
#define FOOBAR_LEN 8
struct fb_data_t {
char name[FOOBAR_LEN + 1];
char value[FOOBAR_LEN + 1];
};
static struct proc_dir_entry *example_dir, *foo_file,
*bar_file, *jiffies_file, *tty_device, *symlink;
struct fb_data_t foo_data, bar_data;
static int proc_read_jiffies(char *page, char **start,
off_t off, int count,
int *eof, void *data)
{
int len;
MOD_INC_USE_COUNT;
len = sprintf(page, "jiffies = %ld\n",
jiffies);
MOD_DEC_USE_COUNT;
return len;
}
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;
MOD_INC_USE_COUNT;
len = sprintf(page, "%s = ’%s’\n",
fb_data->name, fb_data->value);
MOD_DEC_USE_COUNT;
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;
MOD_INC_USE_COUNT;
if(count > FOOBAR_LEN)
len = FOOBAR_LEN;
else
len = count;
if(copy_from_user(fb_data->value, buffer, len)) {
MOD_DEC_USE_COUNT;
return -EFAULT;
}
fb_data->value[len] = ’\0’;
MOD_DEC_USE_COUNT;
return len;
}
static int __init init_procfs_example(void)
{
int rv = 0;
/* create directory */
example_dir = proc_mkdir(MODULE_NAME, NULL);
if(example_dir == NULL) {
rv = -ENOMEM;
goto out;
}
example_dir->owner = THIS_MODULE;
/* create jiffies using convenience function */
jiffies_file = create_proc_read_entry("jiffies",
0444, example_dir,
proc_read_jiffies,
NULL);
if(jiffies_file == NULL) {
rv = -ENOMEM;
goto no_jiffies;
}
jiffies_file->owner = THIS_MODULE;
/* create foo and bar files using same callback
* functions
*/
foo_file = create_proc_entry("foo", 0644, example_dir);
if(foo_file == NULL) {
rv = -ENOMEM;
goto no_foo;
}
strcpy(foo_data.name, "foo");
strcpy(foo_data.value, "foo");
foo_file->data = &foo_data;
foo_file->read_proc = proc_read_foobar;
foo_file->write_proc = proc_write_foobar;
foo_file->owner = THIS_MODULE;
bar_file = create_proc_entry("bar", 0644, example_dir);
if(bar_file == NULL) {
rv = -ENOMEM;
goto no_bar;
}
strcpy(bar_data.name, "bar");
strcpy(bar_data.value, "bar");
bar_file->data = &bar_data;
bar_file->read_proc = proc_read_foobar;
bar_file->write_proc = proc_write_foobar;
bar_file->owner = THIS_MODULE;
/* create tty device */
tty_device = proc_mknod("tty", S_IFCHR | 0666,
example_dir, MKDEV(5, 0));
if(tty_device == NULL) {
rv = -ENOMEM;
goto no_tty;
}
tty_device->owner = THIS_MODULE;
/* create symlink */
symlink = proc_symlink("jiffies_too", example_dir,
"jiffies");
if(symlink == NULL) {
rv = -ENOMEM;
goto no_symlink;
}
symlink->owner = THIS_MODULE;
/* everything OK */
printk(KERN_INFO "%s %s initialised\n",
MODULE_NAME, MODULE_VERSION);
return 0;
no_symlink:
remove_proc_entry("tty", example_dir);
no_tty:
remove_proc_entry("bar", example_dir);
no_bar:
remove_proc_entry("foo", example_dir);
no_foo:
remove_proc_entry("jiffies", example_dir);
no_jiffies:
remove_proc_entry(MODULE_NAME, NULL);
out:
return rv;
}
static void __exit cleanup_procfs_example(void)
{
remove_proc_entry("jiffies_too", example_dir);
remove_proc_entry("tty", example_dir);
remove_proc_entry("bar", example_dir);
remove_proc_entry("foo", example_dir);
remove_proc_entry("jiffies", example_dir);
remove_proc_entry(MODULE_NAME, NULL);
printk(KERN_INFO "%s %s removed\n",
MODULE_NAME, MODULE_VERSION);
}
module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");
EXPORT_NO_SYMBOLS;
The Linux Kernel Module Programming Guide
Chapter 5. The /proc File System
1. The /proc File System
proc_register_dynamic防止kernel节点号冲突
1.定义一个文件结构
struct proc_dir_entry *OurProcFile;
2.创建一个proc文件
OurProcFile = create_proc_entry(PROCFS_NAME,0644,NULL);
3.给proc文件结构赋值
OurProcFile->read_proc = procfile_read; //指定相应读函数
OurProcFile->write_proc = procfile_write; //指定相应写函数
OurProcFile->mode = S_IFREG | S_IRUGO;
OurProcFile->uid = 0;
OurProcFile->gid = 0;
OurProcFile->size = 37;
初始化完成。
2. Read and Write a /proc File
从用户空间到kernel空间数据传递使用copy_from_user or get_user,因为linux内存是分段的,不是一个独立的地址,每一个进程都有一个独立的地址空间。比如:A有0x1234,B也有0x1234;
系统会自动处理kernel的访问和写。
int procfile_read(char *buffer,char **buffer_location,off_t offset, int buffer_length, int *eof, void *data)
{
........................
if (offset > 0) /* we have finished to read, return 0 */
{
ret = 0;
} else /* fill the buffer, return the buffer size */
{
memcpy(buffer, procfs_buffer, procfs_buffer_size);
ret = procfs_buffer_size;
}
return ret;
}
int procfile_write(struct file *file, const char *buffer, unsigned long count,void *data)
{
...............
if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) )
{
return ?EFAULT;
}
return procfs_buffer_size;
}
3. Manage /proc file with standard filesystem
两个结构体比较重要
struct inode_operations 包含了struct file_operations指针
static struct inode_operations Inode_Ops_4_Our_Proc_File =
{
.permission = module_permission, /* check for permissions */
};
int init_module()
{
.............
Our_Proc_File?>proc_iops = &Inode_Ops_4_Our_Proc_File;
Our_Proc_File?>mode = S_IFREG | S_IRUGO | S_IWUSR;
.............
}
static int module_permission(struct inode *inode, int op, struct nameidata *foo)
{
/*
* We allow everybody to read from our module, but
* only root (uid 0) may write to it
*/
if (op == 4 || (op == 2 && current?>euid == 0))
return 0;
/*
* If it's anything else, access is denied
*/
return ?EACCES;
}
4. Manage /proc file with seq_file
使用seq_file库来管理proc
头文件:#include <linux/seq_file.h> /* for seq_file */
static struct seq_operations my_seq_ops = {
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show
};
static struct file_operations my_file_ops = {
.owner = THIS_MODULE,
.open = my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_ops);
}
static int my_seq_show(struct seq_file *s, void *v)
{
loff_t *spos = (loff_t *) v;
seq_printf(s, "%Ld\n", *spos);
return 0;
}
static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
unsigned long *tmp_v = (unsigned long *)v;
(*tmp_v)++;
(*pos)++;
return NULL;
}
static void *my_seq_start(struct seq_file *s, loff_t *pos)
{
static unsigned long counter = 0;
/* beginning a new sequence ? */
if ( *pos == 0 )
{
/* yes => return a non null value to begin the sequence */
return &counter;
}
else
{
/* no => it's the end of the sequence, return end to stop reading */
*pos = 0;
return NULL;
}
}
Essential Linux Device Drivers 附录C翻译
当故障的原因很难确定时,监控和分析由procfs提供的数据可能会提供帮助。
但是,当数据量很大时,procfs的read()实现变得很复杂。Seq 文件接口是一种内核
提供的帮助机制用来简化这样的实现。Seq文件接口使得procfs操作干净又整洁。
让我们先介绍一个逐步变得复杂的procfs read()实现,然后看看Seq文件接口如何将
这个复杂的实现变得优雅。我们也会逐步跟新2.6内核中还没有采用seq file的驱动.
Seq 文件接口的优点
通过一个例子来看看Seq的优点吧。同通常的设备驱动一样,假设你有某一个数据结构的链表,
每一个节点都包含一个字符串域(称为info)。一下代码C1通过一个/proc/readme文件将其输出
到用户空间。当用户读这个文件,procfs的read()方法的具体实现,readme_proc将被调用,它将
遍历链表,将所有节点的info域传递给文件系统缓冲区。
C1:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
MODULE_DESCRIPTION("test proc");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
static int readme_proc(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
off_t thischunk_len = 0;
struct _mydrv_struct *p;
printk( KERN_DEBUG "offset is %d\n",offset );
/* Traverse the list and copy info into the supplied buffer */
list_for_each_entry(p, &mydrv_list, list)
{
thischunk_len += sprintf(page+thischunk_len, p->info);
}
*eof = 1; /* Indicate completion */
return thischunk_len;
}
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->read_proc = readme_proc;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<100;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
使用如下命令
bash> cat /proc/readme
Node No: 0
Node No: 1
...
Node No: 99
当read()方法被调用,它将提供一页的内存(译者注:通常是4k)用来传递信息到用户空间。
正如你所看到的,readme_proc的第一个参数是一个指向1页大小的缓冲区的指针。
第二个参数start用于当数据量超过1页时read方法的实现,在例子C2中我们可以明确它的使用方法。
接下来的两个参数非别用于指定当前读请求的偏移和要读的byte数量
*eof用来告诉调用者是否还需要读更多的数据。如果你注释掉了上面对*eof置一的语句,
readme_proc将被再次调用,且offset被指定为1190(节点0-节点99中info域最终显示的字符串数量)
readme_proc返回此次写入缓冲区的有效字符数。
在例子C1里,procfs read方法的返回被限定在1页以内。如果你将节点数量从100增加到500,将看到以下输出:
bash> cat /proc/readmeNode No: 0Node No: 1...Node No: 322proc_file_read: Apparent buffer overflow!
正如你所看到的,当超过4096byte的ascii字符被制造出来,产生了一个缓冲区溢出
为了解决这个问题,你需要修改c1的代码,使用以前提到的start变量
这将导致代码有点复杂,这样的实现基于以下原理:
1、readme_proc被调用多次,每次调用都将改变两个参数:每次要地区的最大数据量count和相对于
文件开始的偏移。每次的count值必须小于一页
2、每次调用结束后,offset增加readme_proc的返回值的大小
3、readme_proc的eof信号仅仅当总共产生的数据量小于等于本次请求的count+offset才发出。
如果eof没有被设置,readme_proc将再次被调用,传递给它的offset将增加上一次调用readme_proc的
返回值
4、每次调用结束后,只有start指向的数据是有效的并被传递给调用者
为了更好地理解操作过程,
C2代码将打印每次被调用的参数: start、offset、count、page
通过以下修改,你的代码可以摆脱限制,输出大量的信息
bash> cat /proc/readmeNode No: 0Node No: 1...Node No: 499
C2:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
MODULE_DESCRIPTION("test proc");
MODULE_AUTHOR("root ()");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
static int readme_proc(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
int i = 0;
off_t thischunk_start = 0;
off_t thischunk_len = 0;
struct _mydrv_struct *p;
/* Loop thru the list collecting device info */
list_for_each_entry(p, &mydrv_list, list) {
thischunk_len += sprintf(page+thischunk_len, p->info);
/* Advance thischunk_start only to the extent that the next
* read will not result in total bytes more than (offset+count)
*/
if (thischunk_start + thischunk_len < offset) {
thischunk_start += thischunk_len;
thischunk_len = 0;
} else if (thischunk_start + thischunk_len > offset+count) {
break;
} else {
continue;
}
}
/* Actual start */
*start = page + (offset - thischunk_start);
/* Calculate number of written bytes */
thischunk_len -= (offset - thischunk_start);
if (thischunk_len > count) {
thischunk_len = count;
} else {
*eof = 1;
}
return thischunk_len;
}
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->read_proc = readme_proc;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<500;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
当你面对C2中复杂的read实现感到前途黯淡时,Seq 文件接口前来救驾。
顾名思义,seq file interface 将procfs文件内容看做是序列化的对象
(Seq)编程接口提供的是这个序列化对象的迭代器
因此你的Seq代码必须实现以下的迭代器方法
1、start()将被seq interface首先调用,它初始化迭代子对象的位置并且返回插入的第一个迭代子对象
2、next()增加迭代器的位置,返回指向下一个迭代子的指针。这个函数不知道迭代子的内核结构,
将其看做黑盒
3、show()当用户读procfs的文件时,解释传递给它的迭代子,转换成要输出给用户的字符串。
这个方法充分利用了内核提供的 seq_printf(), seq_putc(), and seq_puts() 来格式化输出
4、stop()最后被调用用来清理
响应用户对相关proc文件的操作时,seq file interface会自动调用以上迭代器方法去产生输出。
你不用再去担心页大小和eof信号
C3
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_DESCRIPTION("test proc");
MODULE_AUTHOR("root ()");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
/* start() method */
static void *
mydrv_seq_start(struct seq_file *seq, loff_t *pos)
{
struct _mydrv_struct *p;
loff_t off = 0;
/* The iterator at the requested offset */
list_for_each_entry(p, &mydrv_list, list) {
if (*pos == off++) return p;
}
return NULL;
}
/* next() method */
static void *
mydrv_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
/* 'v' is a pointer to the iterator returned by start() or
by the previous invocation of next() */
struct list_head *n = ((struct _mydrv_struct *)v)->list.next;
++*pos; /* Advance position */
/* Return the next iterator, which is the next node in the list */
return(n != &mydrv_list) ?
list_entry(n, struct _mydrv_struct, list) : NULL;
}
/* show() method */
static int
mydrv_seq_show(struct seq_file *seq, void *v)
{
const struct _mydrv_struct *p = v;
/* Interpret the iterator, 'v' */
seq_printf(seq, p->info);
return 0;
}
/* stop() method */
static void mydrv_seq_stop(struct seq_file *seq, void *v)
{
/* No cleanup needed in this example */
}
/* Define iterator operations */
static struct seq_operations mydrv_seq_ops = {
.start = mydrv_seq_start,
.next = mydrv_seq_next,
.stop = mydrv_seq_stop,
.show = mydrv_seq_show,
};
static int
mydrv_seq_open(struct inode *inode, struct file *file)
{
/* Register the operators */
return seq_open(file, &mydrv_seq_ops);
}
static struct file_operations mydrv_proc_fops = {
.owner = THIS_MODULE,
.open = mydrv_seq_open, /* User supplied */
.read = seq_read, /* Built-in helper function */
.llseek = seq_lseek, /* Built-in helper function */
.release = seq_release, /* Built-in helper funciton */
};
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->proc_fops = &mydrv_proc_fops;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<500;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
注意:如果不需要实现迭代输出的功能,使用single_open函数即可,具体可以看 /fs/proc 目录下的version.c文件,有实现的范例。
Chapter 1. Introduction
/proc文件系统(procfs)在linux内核中是一个特殊的文件系统,是一个虚拟的文件系统,只存在于内存中
注:proc/sys是sysctl文件,不属于procfs
Chapter 2. Managing procfs entries
要使用procfs,需包含#include <linux/proc_fs.h>
1.Creating a regular file
struct proc_dir_entry* create_proc_entry(const char* name,mode_t mode, struct proc_dir_entry* parent);
parent为NULL时,表示文件建立在/proc目录下,失败返回NULL
create_proc_read_entry:创建并初始化一个procfs入口in one single call
2. Creating a symlink
struct proc_dir_entry* proc_symlink(const char* name, struct proc_dir_entry* parent, const char* dest);
在parent目录下创建一个从name到dest的符号链接,像:ln -s dest name
3. Creating a device
struct proc_dir_entry* proc_mknod(const char* name, mode_t mode,struct proc_dir_entry* parent, kdev_t rdev);
创建一个名为name设备文件,mode参数必须包含S_IFBLK或者S_IFCHR,像:mknod --mode=mode name rdev
4. Creating a directory
struct proc_dir_entry* proc_mkdir(const char* name, struct proc_dir_entry* parent);
在parent目录下创建名为name的目录
5.Removing an entry
void remove_proc_entry(const char* name, struct proc_dir_entry* parent);
从procfs中移除parent目录下的name入口。注意它不会递归移除
Chapter 3. Communicating with userland
procfs对文件的读写并不是直接的,而是通过call back函数来操作read_proc and/or write_proc
在create_proc_entry函数返回一个proc_dir_entry* entry,然后设置
entry->read_proc = read_proc_foo;
entry->write_proc = write_proc_foo;
1. Reading data
kernel向用户返回数据
int read_func(char* page, char** start, off_t off, int count,int* eof, void* data);
从start处的off偏移处开始写最多count个字节的内容到page中。
2. Writing data
用户写数据到kernel
int write_func(struct file* file, const char* buffer, unsigned long count, void* data);
从buffer中读count字节,buffer不存在于kernel memory空间,所以需要使用copy_from_user函数,file常NULL
3.A single call back for many files
struct proc_dir_entry* entry;
struct my_file_data *file_data;
file_data = kmalloc(sizeof(struct my_file_data), GFP_KERNEL);
entry->data = file_data;
int foo_read_func(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len;
if(data == file_data) {
/* special case for this file */
} else {
/* normal processing */
}
return len;
}
在 remove_proc_entry是要free掉data
Chapter 4. Tips and tricks
1. Convenience functions
struct proc_dir_entry* create_proc_read_entry(const char* name,mode_t mode, struct proc_dir_entry* parent, read_proc_t* read_proc, void* data);
将只需要读操作的create_proc_entry简化掉了
2. Modules
struct proc_dir_entry* entry;
entry->owner = THIS_MODULE;
目前发现没有这个owner参数了,不用关注。
3. Mode and ownership
设置模式和权限
struct proc_dir_entry* entry;
entry->mode = S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH;
entry->uid = 0;
entry->gid = 100;
Chapter 5. Example
board (http://www.lart.tudelft.nl/), which is
* sponsored by the Mobile Multi-media Communications
* (http://www.mmc.tudelft.nl/) and Ubiquitous Communications
* (http://www.ubicom.tudelft.nl/) projects.
*
* The author can be reached at:
*
* Erik Mouw
* Information and Communication Theory Group
* Faculty of Information Technology and Systems
* Delft University of Technology
* P.O. Box 5031
* 2600 GA Delft
* The Netherlands
*
*
* This program is free software; you can redistribute
* it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place,* Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#define MODULE_VERSION "1.0"
#define MODULE_NAME "procfs_example"
#define FOOBAR_LEN 8
struct fb_data_t {
char name[FOOBAR_LEN + 1];
char value[FOOBAR_LEN + 1];
};
static struct proc_dir_entry *example_dir, *foo_file,
*bar_file, *jiffies_file, *tty_device, *symlink;
struct fb_data_t foo_data, bar_data;
static int proc_read_jiffies(char *page, char **start,
off_t off, int count,
int *eof, void *data)
{
int len;
MOD_INC_USE_COUNT;
len = sprintf(page, "jiffies = %ld\n",
jiffies);
MOD_DEC_USE_COUNT;
return len;
}
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;
MOD_INC_USE_COUNT;
len = sprintf(page, "%s = ’%s’\n",
fb_data->name, fb_data->value);
MOD_DEC_USE_COUNT;
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;
MOD_INC_USE_COUNT;
if(count > FOOBAR_LEN)
len = FOOBAR_LEN;
else
len = count;
if(copy_from_user(fb_data->value, buffer, len)) {
MOD_DEC_USE_COUNT;
return -EFAULT;
}
fb_data->value[len] = ’\0’;
MOD_DEC_USE_COUNT;
return len;
}
static int __init init_procfs_example(void)
{
int rv = 0;
/* create directory */
example_dir = proc_mkdir(MODULE_NAME, NULL);
if(example_dir == NULL) {
rv = -ENOMEM;
goto out;
}
example_dir->owner = THIS_MODULE;
/* create jiffies using convenience function */
jiffies_file = create_proc_read_entry("jiffies",
0444, example_dir,
proc_read_jiffies,
NULL);
if(jiffies_file == NULL) {
rv = -ENOMEM;
goto no_jiffies;
}
jiffies_file->owner = THIS_MODULE;
/* create foo and bar files using same callback
* functions
*/
foo_file = create_proc_entry("foo", 0644, example_dir);
if(foo_file == NULL) {
rv = -ENOMEM;
goto no_foo;
}
strcpy(foo_data.name, "foo");
strcpy(foo_data.value, "foo");
foo_file->data = &foo_data;
foo_file->read_proc = proc_read_foobar;
foo_file->write_proc = proc_write_foobar;
foo_file->owner = THIS_MODULE;
bar_file = create_proc_entry("bar", 0644, example_dir);
if(bar_file == NULL) {
rv = -ENOMEM;
goto no_bar;
}
strcpy(bar_data.name, "bar");
strcpy(bar_data.value, "bar");
bar_file->data = &bar_data;
bar_file->read_proc = proc_read_foobar;
bar_file->write_proc = proc_write_foobar;
bar_file->owner = THIS_MODULE;
/* create tty device */
tty_device = proc_mknod("tty", S_IFCHR | 0666,
example_dir, MKDEV(5, 0));
if(tty_device == NULL) {
rv = -ENOMEM;
goto no_tty;
}
tty_device->owner = THIS_MODULE;
/* create symlink */
symlink = proc_symlink("jiffies_too", example_dir,
"jiffies");
if(symlink == NULL) {
rv = -ENOMEM;
goto no_symlink;
}
symlink->owner = THIS_MODULE;
/* everything OK */
printk(KERN_INFO "%s %s initialised\n",
MODULE_NAME, MODULE_VERSION);
return 0;
no_symlink:
remove_proc_entry("tty", example_dir);
no_tty:
remove_proc_entry("bar", example_dir);
no_bar:
remove_proc_entry("foo", example_dir);
no_foo:
remove_proc_entry("jiffies", example_dir);
no_jiffies:
remove_proc_entry(MODULE_NAME, NULL);
out:
return rv;
}
static void __exit cleanup_procfs_example(void)
{
remove_proc_entry("jiffies_too", example_dir);
remove_proc_entry("tty", example_dir);
remove_proc_entry("bar", example_dir);
remove_proc_entry("foo", example_dir);
remove_proc_entry("jiffies", example_dir);
remove_proc_entry(MODULE_NAME, NULL);
printk(KERN_INFO "%s %s removed\n",
MODULE_NAME, MODULE_VERSION);
}
module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");
EXPORT_NO_SYMBOLS;
The Linux Kernel Module Programming Guide
Chapter 5. The /proc File System
1. The /proc File System
proc_register_dynamic防止kernel节点号冲突
1.定义一个文件结构
struct proc_dir_entry *OurProcFile;
2.创建一个proc文件
OurProcFile = create_proc_entry(PROCFS_NAME,0644,NULL);
3.给proc文件结构赋值
OurProcFile->read_proc = procfile_read; //指定相应读函数
OurProcFile->write_proc = procfile_write; //指定相应写函数
OurProcFile->mode = S_IFREG | S_IRUGO;
OurProcFile->uid = 0;
OurProcFile->gid = 0;
OurProcFile->size = 37;
初始化完成。
2. Read and Write a /proc File
从用户空间到kernel空间数据传递使用copy_from_user or get_user,因为linux内存是分段的,不是一个独立的地址,每一个进程都有一个独立的地址空间。比如:A有0x1234,B也有0x1234;
系统会自动处理kernel的访问和写。
int procfile_read(char *buffer,char **buffer_location,off_t offset, int buffer_length, int *eof, void *data)
{
........................
if (offset > 0) /* we have finished to read, return 0 */
{
ret = 0;
} else /* fill the buffer, return the buffer size */
{
memcpy(buffer, procfs_buffer, procfs_buffer_size);
ret = procfs_buffer_size;
}
return ret;
}
int procfile_write(struct file *file, const char *buffer, unsigned long count,void *data)
{
...............
if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) )
{
return ?EFAULT;
}
return procfs_buffer_size;
}
3. Manage /proc file with standard filesystem
两个结构体比较重要
struct inode_operations 包含了struct file_operations指针
static struct inode_operations Inode_Ops_4_Our_Proc_File =
{
.permission = module_permission, /* check for permissions */
};
int init_module()
{
.............
Our_Proc_File?>proc_iops = &Inode_Ops_4_Our_Proc_File;
Our_Proc_File?>mode = S_IFREG | S_IRUGO | S_IWUSR;
.............
}
static int module_permission(struct inode *inode, int op, struct nameidata *foo)
{
/*
* We allow everybody to read from our module, but
* only root (uid 0) may write to it
*/
if (op == 4 || (op == 2 && current?>euid == 0))
return 0;
/*
* If it's anything else, access is denied
*/
return ?EACCES;
}
4. Manage /proc file with seq_file
使用seq_file库来管理proc
头文件:#include <linux/seq_file.h> /* for seq_file */
static struct seq_operations my_seq_ops = {
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show
};
static struct file_operations my_file_ops = {
.owner = THIS_MODULE,
.open = my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_ops);
}
static int my_seq_show(struct seq_file *s, void *v)
{
loff_t *spos = (loff_t *) v;
seq_printf(s, "%Ld\n", *spos);
return 0;
}
static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
unsigned long *tmp_v = (unsigned long *)v;
(*tmp_v)++;
(*pos)++;
return NULL;
}
static void *my_seq_start(struct seq_file *s, loff_t *pos)
{
static unsigned long counter = 0;
/* beginning a new sequence ? */
if ( *pos == 0 )
{
/* yes => return a non null value to begin the sequence */
return &counter;
}
else
{
/* no => it's the end of the sequence, return end to stop reading */
*pos = 0;
return NULL;
}
}
Essential Linux Device Drivers 附录C翻译
当故障的原因很难确定时,监控和分析由procfs提供的数据可能会提供帮助。
但是,当数据量很大时,procfs的read()实现变得很复杂。Seq 文件接口是一种内核
提供的帮助机制用来简化这样的实现。Seq文件接口使得procfs操作干净又整洁。
让我们先介绍一个逐步变得复杂的procfs read()实现,然后看看Seq文件接口如何将
这个复杂的实现变得优雅。我们也会逐步跟新2.6内核中还没有采用seq file的驱动.
Seq 文件接口的优点
通过一个例子来看看Seq的优点吧。同通常的设备驱动一样,假设你有某一个数据结构的链表,
每一个节点都包含一个字符串域(称为info)。一下代码C1通过一个/proc/readme文件将其输出
到用户空间。当用户读这个文件,procfs的read()方法的具体实现,readme_proc将被调用,它将
遍历链表,将所有节点的info域传递给文件系统缓冲区。
C1:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
MODULE_DESCRIPTION("test proc");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
static int readme_proc(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
off_t thischunk_len = 0;
struct _mydrv_struct *p;
printk( KERN_DEBUG "offset is %d\n",offset );
/* Traverse the list and copy info into the supplied buffer */
list_for_each_entry(p, &mydrv_list, list)
{
thischunk_len += sprintf(page+thischunk_len, p->info);
}
*eof = 1; /* Indicate completion */
return thischunk_len;
}
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->read_proc = readme_proc;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<100;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
使用如下命令
bash> cat /proc/readme
Node No: 0
Node No: 1
...
Node No: 99
当read()方法被调用,它将提供一页的内存(译者注:通常是4k)用来传递信息到用户空间。
正如你所看到的,readme_proc的第一个参数是一个指向1页大小的缓冲区的指针。
第二个参数start用于当数据量超过1页时read方法的实现,在例子C2中我们可以明确它的使用方法。
接下来的两个参数非别用于指定当前读请求的偏移和要读的byte数量
*eof用来告诉调用者是否还需要读更多的数据。如果你注释掉了上面对*eof置一的语句,
readme_proc将被再次调用,且offset被指定为1190(节点0-节点99中info域最终显示的字符串数量)
readme_proc返回此次写入缓冲区的有效字符数。
在例子C1里,procfs read方法的返回被限定在1页以内。如果你将节点数量从100增加到500,将看到以下输出:
bash> cat /proc/readmeNode No: 0Node No: 1...Node No: 322proc_file_read: Apparent buffer overflow!
正如你所看到的,当超过4096byte的ascii字符被制造出来,产生了一个缓冲区溢出
为了解决这个问题,你需要修改c1的代码,使用以前提到的start变量
这将导致代码有点复杂,这样的实现基于以下原理:
1、readme_proc被调用多次,每次调用都将改变两个参数:每次要地区的最大数据量count和相对于
文件开始的偏移。每次的count值必须小于一页
2、每次调用结束后,offset增加readme_proc的返回值的大小
3、readme_proc的eof信号仅仅当总共产生的数据量小于等于本次请求的count+offset才发出。
如果eof没有被设置,readme_proc将再次被调用,传递给它的offset将增加上一次调用readme_proc的
返回值
4、每次调用结束后,只有start指向的数据是有效的并被传递给调用者
为了更好地理解操作过程,
C2代码将打印每次被调用的参数: start、offset、count、page
通过以下修改,你的代码可以摆脱限制,输出大量的信息
bash> cat /proc/readmeNode No: 0Node No: 1...Node No: 499
C2:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
MODULE_DESCRIPTION("test proc");
MODULE_AUTHOR("root ()");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
static int readme_proc(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
int i = 0;
off_t thischunk_start = 0;
off_t thischunk_len = 0;
struct _mydrv_struct *p;
/* Loop thru the list collecting device info */
list_for_each_entry(p, &mydrv_list, list) {
thischunk_len += sprintf(page+thischunk_len, p->info);
/* Advance thischunk_start only to the extent that the next
* read will not result in total bytes more than (offset+count)
*/
if (thischunk_start + thischunk_len < offset) {
thischunk_start += thischunk_len;
thischunk_len = 0;
} else if (thischunk_start + thischunk_len > offset+count) {
break;
} else {
continue;
}
}
/* Actual start */
*start = page + (offset - thischunk_start);
/* Calculate number of written bytes */
thischunk_len -= (offset - thischunk_start);
if (thischunk_len > count) {
thischunk_len = count;
} else {
*eof = 1;
}
return thischunk_len;
}
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->read_proc = readme_proc;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<500;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
当你面对C2中复杂的read实现感到前途黯淡时,Seq 文件接口前来救驾。
顾名思义,seq file interface 将procfs文件内容看做是序列化的对象
(Seq)编程接口提供的是这个序列化对象的迭代器
因此你的Seq代码必须实现以下的迭代器方法
1、start()将被seq interface首先调用,它初始化迭代子对象的位置并且返回插入的第一个迭代子对象
2、next()增加迭代器的位置,返回指向下一个迭代子的指针。这个函数不知道迭代子的内核结构,
将其看做黑盒
3、show()当用户读procfs的文件时,解释传递给它的迭代子,转换成要输出给用户的字符串。
这个方法充分利用了内核提供的 seq_printf(), seq_putc(), and seq_puts() 来格式化输出
4、stop()最后被调用用来清理
响应用户对相关proc文件的操作时,seq file interface会自动调用以上迭代器方法去产生输出。
你不用再去担心页大小和eof信号
C3
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_DESCRIPTION("test proc");
MODULE_AUTHOR("root ()");
MODULE_LICENSE("GPL");
/* Private Data structure */
struct _mydrv_struct
{
/* ... */
struct list_head list; /* Link to the next node */
char info[10]; /* Info to pass via the procfs file */
/* ... */
};
static LIST_HEAD(mydrv_list); /* List Head */
/* start() method */
static void *
mydrv_seq_start(struct seq_file *seq, loff_t *pos)
{
struct _mydrv_struct *p;
loff_t off = 0;
/* The iterator at the requested offset */
list_for_each_entry(p, &mydrv_list, list) {
if (*pos == off++) return p;
}
return NULL;
}
/* next() method */
static void *
mydrv_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
/* 'v' is a pointer to the iterator returned by start() or
by the previous invocation of next() */
struct list_head *n = ((struct _mydrv_struct *)v)->list.next;
++*pos; /* Advance position */
/* Return the next iterator, which is the next node in the list */
return(n != &mydrv_list) ?
list_entry(n, struct _mydrv_struct, list) : NULL;
}
/* show() method */
static int
mydrv_seq_show(struct seq_file *seq, void *v)
{
const struct _mydrv_struct *p = v;
/* Interpret the iterator, 'v' */
seq_printf(seq, p->info);
return 0;
}
/* stop() method */
static void mydrv_seq_stop(struct seq_file *seq, void *v)
{
/* No cleanup needed in this example */
}
/* Define iterator operations */
static struct seq_operations mydrv_seq_ops = {
.start = mydrv_seq_start,
.next = mydrv_seq_next,
.stop = mydrv_seq_stop,
.show = mydrv_seq_show,
};
static int
mydrv_seq_open(struct inode *inode, struct file *file)
{
/* Register the operators */
return seq_open(file, &mydrv_seq_ops);
}
static struct file_operations mydrv_proc_fops = {
.owner = THIS_MODULE,
.open = mydrv_seq_open, /* User supplied */
.read = seq_read, /* Built-in helper function */
.llseek = seq_lseek, /* Built-in helper function */
.release = seq_release, /* Built-in helper funciton */
};
static int test_proc_init_module(void)
{
int i;
static struct proc_dir_entry *entry = NULL ;
struct _mydrv_struct * mydrv_new;
/* Create /proc/readme */
entry = create_proc_entry("readme", S_IWUSR, NULL);
if (entry)
{
entry->proc_fops = &mydrv_proc_fops;
}
/* Handcraft mydrv_list for testing purpose.
In the real world, device driver logic
maintains the list and populates the 'info' field */
for (i=0;i<500;i++)
{
mydrv_new = kmalloc(sizeof(struct _mydrv_struct), GFP_ATOMIC);
sprintf(mydrv_new->info, "Node No: %d\n", i);
list_add_tail(&mydrv_new->list, &mydrv_list);
}
return 0;
}
static void test_proc_exit_module(void)
{
remove_proc_entry("readme",0);
printk( KERN_DEBUG "Module test_proc exit\n" );
}
module_init(test_proc_init_module);
module_exit(test_proc_exit_module);
注意:如果不需要实现迭代输出的功能,使用single_open函数即可,具体可以看 /fs/proc 目录下的version.c文件,有实现的范例。
相关文章推荐
- Linux procfs开发指南(部分翻译并做了精简)
- (转载) Linux汇编语言开发指南(zt)
- 转载——Linux 汇编语言开发指南
- AutoCAD .Net开发指南第4部分关于标注和公差的内容翻译完了!
- (转载)Linux 汇编语言开发指南
- (转载)Linux 汇编语言(GNU GAS汇编)开发指南
- 【转载】在 Linux 下用户空间与内核空间数据交换的方式,第 2 部分: procfs、seq_file、debugfs和relayfs
- Linux 汇编语言开发指南(转载)
- 【转载】在 Linux 下用户空间与内核空间数据交换的方式,第 2 部分: procfs、seq_file、debugfs和relayfs
- 【GamePlay3D】开发指南——部分翻译
- [翻译]现代java开发指南 第二部分
- Android帮助文档翻译——开发指南(三)任务和Back Stack
- Android 开发指南 翻译11 Application Resources -- Drawable Resources
- 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(三)
- Linux 汇编语言开发指南
- NDK官方开发指南翻译之 CPU_Features
- 友坚三星4412开发板Linux平台下UT4412BV03裸机开发指南
- Linux平台下4412开发板开发板裸机开发指南 01
- Xqk.Data数据框架开发指南:丰富的、灵活的查询方法(第二部分:适应不同数据库系统的查询)
- 转载_使用DDD+GDB开发ARM Linux程序