利用proc 实现内核和用户态交换数据
最近写程序需要内核得到用户态的参数,比较苦逼幸福的是虽然ioctrl 用不了,可以用proc实现,proc文件系统提供了一种内核和用户态交互的方法。
proc文件系统的详细接口看<linux/proc_fs.h>
主要需要关注的是这几个函数:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent) struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent); void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
函数 proc_mkdir用于在proc文件系统下创建文件夹, proc_mkdir的第一个参数是要创建的目录的名字,第二个参数是指向父目录结构的指针。需要注意一下几点:
1. 创建过程没有对于名字的检查,完全可以调用proc_mkdir 创建出一堆同样名字的文件(检查一下会死啊!)
2. 其实名字可以包含路径,例如“dev/new_proc”, 如果存在dev目录,函数会在dev目录下创建new_proc目录。
3. 如果第二个参数是NULL,会在根目录也就是/proc/目录下创建目录
函数的返回值就是 新创建目录对应的proc_dir_entry, 保存这个就可以用来在此目录下创建文件啦,其实,即使不保存,利用上面介绍的第二点性质也可以在目录下创建文件,而且删除目录只用知道路径就可以了~
函数create_proc_entry用来创建文件,mode 参数如果为NULL的话默认的文件访问权限是 755,其他的参数与proc_mkdir 类似
函数remove_proc_entry用来删除创建的目录或者文件,有意思的是,这个函数只需要知道名字和父目录就可以删除了。
说了这么多,怎么交换数据尼?
需要了解下proc_dir_entry 的结构了
struct proc_dir_entry { unsigned int low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; loff_t size; struct inode_operations * proc_iops; const struct file_operations * proc_fops; get_info_t *get_info; struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /* use count */ int deleted; /* delete flag */ void *set; };
其他的参数可以忽略,这里需要注意的是两个成员变量:
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);
对proc文件的读写操作,最终将转化为对read_proc和write_proc的调用。
看到这里明白了吧,只要在内核里注册proc文件,实现read_proc 和write_proc函数,然后设置proc_dir_entry对应成员变量的值,在用户态进行读写就可以和内核交互了
解释这两个函数的参数:
对于read_proc_t
1. 第一个参数:为啥叫page?答案就是如果对proc文件调用读操作,内核会分配一个页大小的缓冲区。如何输出大于一个页的数据呢,这得依赖于第二个和第三个参数了。
为了理解第二三个参数,回忆下与文件操作相关的系统调用:
int open(const char *pathname, int flags); off_t lseek(int fildes, off_t offset, int whence); ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);对于proc文件,一次read操作最多只能读取一个page的数据,如果需要读取大于一个页的数据需要保存read的返回值,然后使用lseek设定offset,然后再次调用read。回到参数的说明:
2. start和off参数:off对应于lseek里面的offset(lseek whence为SEEK_END,offset为负 的情况下,传进来的off为零,具体原因待考古)。
如果不设置*start的值,off的取值只能在[0, count - 1]之间,且能够读取的数据大小为:count - off。可以理解系统拷贝了 [page + off, page+count - 1]之间的数据到用户的buffer里。如果off的取值超出范围,read将读不到数据。
如果设置了*start的值,系统认为*start指向的地址就是off指定的地址,off的值会被忽略,系统会拷贝[start, start+count-1]之间的数据到用户空间。当然我们在实现start地址的定位时,可能会需要off的值。
3. count 参数与read中的count一致
4. eof参数,设置了这个参数表明不想再提供数据了,神马意思呢?
如果不设置这个参数,对于上面说的start为空的情况,read_proc返回后,系统如果发现 (count - off) < count 会接着下发写请求,读取off大小的数据。
例如:有这样的读取操作:
lseek(fd, 2, SEEK_SET); read(fd, buff_r, 30)
第一次调用read_proc, off=2, count=30, 由于我们没有设置start的值,将读取28字节的数据,由于没有设置*eof, 系统会再次下发read_proc
第二次调用read_proc, off=30, count=2, 在参数start的说明里提到,对于这个调用,系统默认读不到数据(怨念啊,为啥要下发)
于是read返回28
如果设置了这个参数就不会有第二次的下发了。
5. data参数,这个是给驱动程序预留的参数。
对于write_proc函数,参数是很简单的,需要说明的只有一点,就是write_proc的第二个参数buffer是用户态的地址,需要用copy_from_user从用户态把数据拷到内核态的缓冲区里。