一、proc文件系统的作用
可以用于内核态和用户态之间的数据交互,常用在字符设备驱动程序中用于控制驱动的行为,获取内核数据。
测试内核版本:5.15.77
proc接口使用示例:
参考示例:
$ echo “vfs_read 0” > /proc/hook_targets # disable vfs_read hooking
$ echo “vfs_read 1” > /proc/hook_targets # enable vfs_read hooking
二、常用函数
在parent目录下创建名为name的文件,如果该参数为NULL,默认在/proc目录下创建名为name的文件。
/*
* @ name: 文件名
* @ mode: 节点访问权限,UGO的模式来表示
* @ parent: 指向父目录的proc_dir_entry指针
* @ proc_ops: 指向该文件的操作函数,open/read/write/release
*/
struct proc_dir_entry *proc_create(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct proc_ops *proc_ops)
{
return proc_create_data(name, mode, parent, proc_ops, NULL);
}
EXPORT_SYMBOL(proc_create);
在parent父目录下下创建文件夹,如果parent参数为NULL,默认在/proc目录下创建名为name的文件夹。
/*
* @ name: 目录名
* @ parent: 指向父目录的proc_dir_entry指针
*/
struct proc_dir_entry *proc_mkdir(const char *name,
struct proc_dir_entry *parent)
{
return proc_mkdir_data(name, 0, parent, NULL);
}
EXPORT_SYMBOL(proc_mkdir);
移除/proc目录下创建的文件或者是文件夹。
/*
* Remove a /proc entry and free it if it's not currently in use.
* @ name: 文件名
* @ parent: 指向父目录的proc_dir_entry指针,如果该参数为NULL,默认在/proc下查找名为name的文件并删除
*/
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
{
struct proc_dir_entry *de = NULL;
const char *fn = name;
unsigned int len;
write_lock(&proc_subdir_lock);
if (__xlate_proc_name(name, &parent, &fn) != 0) {
write_unlock(&proc_subdir_lock);
return;
}
len = strlen(fn);
de = pde_subdir_find(parent, fn, len);
if (de) {
if (unlikely(pde_is_permanent(de))) {
WARN(1, "removing permanent /proc entry '%s'", de->name);
de = NULL;
} else {
rb_erase(&de->subdir_node, &parent->subdir);
if (S_ISDIR(de->mode))
parent->nlink--;
}
}
write_unlock(&proc_subdir_lock);
if (!de) {
WARN(1, "name '%s'\n", name);
return;
}
proc_entry_rundown(de);
WARN(pde_subdir_first(de),
"%s: removing non-empty directory '%s/%s', leaking at least '%s'\n",
__func__, de->parent->name, de->name, pde_subdir_first(de)->name);
pde_put(de);
}
EXPORT_SYMBOL(remove_proc_entry);
/*
* Remove a /proc entry and free it if it's not currently in use.
* @ name: 文件/目录
* @ parent: 指向文件路径的proc_dir_entry指针
*/
void proc_remove(struct proc_dir_entry *de)
{
if (de)
remove_proc_subtree(de->name, de->parent);
}
EXPORT_SYMBOL(proc_remove);
proc_ops结构体,可以根据具体需求实现相应ops。
// include/linux/proc_fs.h
struct proc_ops {
unsigned int proc_flags;
int (*proc_open)(struct inode *, struct file *);
ssize_t (*proc_read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*proc_read_iter)(struct kiocb *, struct iov_iter *);
ssize_t (*proc_write)(struct file *, const char __user *, size_t, loff_t *);
loff_t (*proc_lseek)(struct file *, loff_t, int);
int (*proc_release)(struct inode *, struct file *);
__poll_t (*proc_poll)(struct file *, struct poll_table_struct *);
long (*proc_ioctl)(struct file *, unsigned int, unsigned long);
#ifdef CONFIG_COMPAT
long (*proc_compat_ioctl)(struct file *, unsigned int, unsigned long);
#endif
int (*proc_mmap)(struct file *, struct vm_area_struct *);
unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
} __randomize_layout;
三、使用实例
proc_interface.c
#include <linux/proc_fs.h>
static struct proc_dir_entry *parent;
static struct proc_dir_entry *node;
static int hook_open(struct inode *inode, struct file *file)
{
return 0;
}
static const struct proc_ops hook_fops = {
.proc_open = hook_open,
// .proc_read = hook_read,
// .proc_write = hook_write,
// .proc_release = hook_release,
};
int proc_interface_init(void)
{
parent = proc_mkdir("hook", NULL);
if (IS_ERR(parent)) {
printk("Create /proc/hook dir failed\n");
return -1;
}
node = proc_create("hook_control", 0, parent, &hook_fops);
if (IS_ERR(node)) {
printk("Create /proc/hook/hook_control failed\n");
goto out;
}
return 0;
out:
remove_proc_entry("hook", NULL);
return -1;
}
void proc_interface_exit(void)
{
remove_proc_entry("hook", NULL);
}
卸载驱动的时候报错,大概意思就是在移除/proc/hook文件夹的时候,没有移除/hook_control文件,导致资源泄露。
[ 2152.885125] ------------[ cut here ]------------
[ 2152.885127] remove_proc_entry: removing non-empty directory '/proc/hook', leaking at least 'hook_control'
[ 2152.885136] WARNING: CPU: 7 PID: 5374 at fs/proc/generic.c:720 remove_proc_entry+0x175/0x190
[ 2152.885175] Modules linked in: proc_control(OE-) vfs_monitor(OE) bnep nfnetlink_queue nfnetlink_log nfnetlink intel_rapl_msr intel_rapl_common vsock_loopback bluetooth vmw_vsock_virtio_transport_common ecdh_generic kvm_intel st ecc cfg80211 kvm crct10dif_pclmul vmw_vsock_vmci_transport crc32_pclmul snd_ens1371 vsock snd_ac97_codec ghash_clmulni_intel gameport snd_rawmidi aesni_intel snd_seq_device crypto_simd ac97_bus binfmt_misc cryptd rapl snd_pcm snd_timer vmw_balloon joydev snd input_leds soundcore pcspkr serio_raw vmw_vmci mac_hid ip_tables x_tables autofs4 btrfs blake2b_generic zstd_compress raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor raid6_pq libcrc32c raid1 raid0 multipath linear hid_generic usbmouse vmwgfx ttm drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops mptspi cec mptscsih mptbase psmouse usbhid e1000 scsi_transport_spi drm i2c_piix4 pata_acpi
[ 2152.885209] CPU: 7 PID: 5374 Comm: rmmod Tainted: G OE 5.15.77-amd64-desktop #1
[ 2152.885210] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 11/12/2020
[ 2152.885211] RIP: 0010:remove_proc_entry+0x175/0x190
[ 2152.885213] Code: c7 68 0b 7f b4 48 85 c0 48 8d 90 78 ff ff ff 48 0f 45 c2 48 8b 53 78 4c 8b 80 a0 00 00 00 48 8b 92 a0 00 00 00 e8 59 00 81 00 <0f> 0b e9 59 ff ff ff e8 8f b4 86 00 66 66 2e 0f 1f 84 00 00 00 00
[ 2152.885214] RSP: 0018:ffffaa34920ebe78 EFLAGS: 00010286
[ 2152.885215] RAX: 0000000000000000 RBX: ffff985fd7c6c480 RCX: 0000000000000027
[ 2152.885216] RDX: 0000000000000027 RSI: ffff9860f5fe0580 RDI: ffff9860f5fe0588
[ 2152.885216] RBP: ffffaa34920ebea8 R08: 0000000000000000 R09: c0000000ffff7fff
[ 2152.885217] R10: 0000000000000001 R11: ffffaa34920ebc48 R12: ffff985fd7c6c500
[ 2152.885217] R13: ffffffffb4c0cd60 R14: 0000000000000000 R15: 0000000000000000
[ 2152.885218] FS: 00007f889fdf4480(0000) GS:ffff9860f5fc0000(0000) knlGS:0000000000000000
[ 2152.885219] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 2152.885219] CR2: 00005573cee94618 CR3: 00000001089de005 CR4: 0000000000770ee0
[ 2152.885238] PKRU: 55555554
[ 2152.885239] Call Trace:
[ 2152.885239] <TASK>
[ 2152.885241] proc_interface_exit+0x17/0x1d [proc_control]
[ 2152.885243] proc_fs_exit+0x9/0x1b [proc_control]
[ 2152.885244] __x64_sys_delete_module+0x144/0x260
[ 2152.885258] ? exit_to_user_mode_prepare+0x32/0x1d0
[ 2152.885261] do_syscall_64+0x37/0xc0
[ 2152.885264] entry_SYSCALL_64_after_hwframe+0x61/0xcb
[ 2152.885267] RIP: 0033:0x7f889ff14f47
[ 2152.885268] Code: 73 01 c3 48 8b 0d 49 0f 0c 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 b8 b0 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 19 0f 0c 00 f7 d8 64 89 01 48
[ 2152.885269] RSP: 002b:00007ffcae8a09b8 EFLAGS: 00000206 ORIG_RAX: 00000000000000b0
[ 2152.885270] RAX: ffffffffffffffda RBX: 00005573cee89760 RCX: 00007f889ff14f47
[ 2152.885271] RDX: 000000000000000a RSI: 0000000000000800 RDI: 00005573cee897c8
[ 2152.885271] RBP: 0000000000000000 R08: 00007ffcae89f931 R09: 0000000000000000
[ 2152.885272] R10: 00007f889ff86ae0 R11: 0000000000000206 R12: 00007ffcae8a0be0
[ 2152.885272] R13: 00007ffcae8a241c R14: 00005573cee89260 R15: 00005573cee89760
[ 2152.885273] </TASK>
[ 2152.885274] ---[ end trace a5e9dcdeed79f436 ]---
[ 2152.885275] Module exit
修改方法:先移除文件,然后再移除文件夹,或者是使用proc_remove
移除对应文件夹。
// 方法一:
void proc_interface_exit(void)
{
if (node != NULL) {
remove_proc_entry(node);
remove_proc_entry(parent);
}
}
//方法二:
void proc_interface_exit(void)
{
if (parent != NULL) {
proc_remove(parent);
}
}
如何让类似于/proc/meminfo
的信息,在cat /proc/meminfo
时在用户态终端打印。
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
{
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
int res = -ENOMEM;
// seq_operations start next stop被初始化为默认函数
// show 方法为single_open 传入的函数指针
if (op) {
op->start = single_start;
op->next = single_next;
op->stop = single_stop;
op->show = show;
res = seq_open(file, op);
if (!res)
// 将file指针的私有数据类型(void *private_data)转换成指向seq_file类型的指针,并将传入
((struct seq_file *)file->private_data)->private = data;
else
kfree(op);
}
return res;
}
EXPORT_SYMBOL(single_open);
seq_printf()
可以将内核信息打印到用户态终端。
void seq_printf(struct seq_file *m, const char *f, ...)
{
va_list args;
va_start(args, f);
seq_vprintf(m, f, args);
va_end(args);
}
EXPORT_SYMBOL(seq_printf);
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static struct proc_dir_entry *parent;
static struct proc_dir_entry *node;
// single_open的回调函数,实现了seq_operaions的show实例
static int show_proc_info(struct seq_file *p, void *v)
{
seq_printf(p, "%s\n", "Seq_printf test string");
return 0;
}
static int hook_open(struct inode *inode, struct file *file)
{
return single_open(file, show_proc_info, NULL);
}
static ssize_t hook_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
return 0;
}
static int hook_release(struct inode *inode, struct file *file)
{
return single_release(inode, file);
}
// .proc_read初始化为seq_read,如果自己实现,将会导致seq_printf失效。
static const struct proc_ops hook_fops = {
.proc_open = hook_open,
.proc_read = seq_read,
.proc_write = hook_write,
.proc_release = hook_release,
};
int proc_interface_init(void)
{
parent = proc_mkdir("hook", NULL);
if (IS_ERR(parent)) {
printk("Create /proc/hook dir failed\n");
return -1;
}
node = proc_create("hook_control", 0, parent, &hook_fops);
if (IS_ERR(node)) {
printk("Create /proc/hook/hook_control failed\n");
goto out;
}
return 0;
out:
remove_proc_entry("hook", NULL);
return -1;
}
void proc_interface_exit(void)
{
remove_proc_entry("hook", NULL);
}
module.c
#include <linux/module.h>
extern int proc_interface_init(void);
extern void proc_interface_exit(void);
static int __init proc_fs_init(void)
{
int ret = 0;
printk("Module init\n");
ret = proc_interface_init();
if (ret != 0) {
return -1;
}
return 0;
}
static void __exit proc_fs_exit(void)
{
proc_interface_exit();
printk("Module exit\n");
}
module_init(proc_fs_init);
module_exit(proc_fs_exit);
MODULE_LICENSE("GPL");
Makefile
obj-m := proc_control.o
proc_control-y += module.o
proc_control-y += proc_interface.o
BASEINCLUDE ?= /lib/modules/`uname -r`/build
all:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;
clean:
$(MAKE) -C $(BASEINCLUDE) M=$(PWD) clean;
rm -f *.ko;
curtis@curtis-PC:~/work/procfs$ sudo insmod proc_control.ko
curtis@curtis-PC:~/work/procfs$ cat /proc/hook/hook_control
Seq_printf test string
下一篇一起学一下如何解析用户态通过echo
传入的参数。