procfs

一、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传入的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值