【linux API分析】proc_create() 及初识proc

PROC

  • proc介绍
  • 创建/proc节点
  • proc代码实例

proc介绍

  在linux系统中,“/proc”文件系统十分有用,它被内核用于向用户导出信息。“/proc”文件系统是一个虚拟文件系统,通过它可以在linux内核空间和用户空间之间进行通信。在/proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,与普通文件不同的是,这些虚拟文件的内容是动态创建的。(/proc文件系统中的文件是动态创建的,驱动加载的时候创建,关机或卸载的时候删除,与我们使用touch命令创建的文件不一样,touch创建的文件只存在于用户空间,且如果不使用rm等删除的话,会一直保留,且/proc文件可以用在内核空间和用户空间之间进行通信,比如用于tft显示屏的帧缓冲驱动)
  "/proc"下的绝大多数文件是只读的,以显示内核信息为主。但是“/proc”下的文件也并不是完全只读的,若节点可写,还可用于一定的控制或配置目的。例如,通过写/proc/sys/kernel/printk可以改变printk()的打印级别。
  linux系统的许多命令本身都是通过分析“/proc”下的文件来完成的,如ps、top、uptime和free等。例如,free命令通过分析/proc/meminfo文件得到可用内存信息,下面显示了对应的meminfo文件和free命令的结果
【linux API分析】proc_create() 及初识proc_第1张图片
在这里插入图片描述
/proc目录下常见的文件介绍:

  • /proc/apm高级电源管理(APM)版本信息及电池相关状态信息,通常由apm命令使用;
  • /proc/buddyinfo用于诊断内存碎片问题的相关信息文件;
  • /proc/cmdline在启动内核时传递至内核的相关参数信息,这些信息通常由lilo或grup等启动管理工具进行传递;
  • /proc/cpuinfo处理器的相关信息的文件;
  • /proc/crypto系统上已安装的内核使用的密码算法及每个算法的详细信息列表;
  • /proc/devices系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;
  • /proc/dma每个正在使用且注册的ISA DMA通道的信息列表;
  • /proc/fb 帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息;
  • /proc/filesystems 当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;
  • /proc/interrupts ARM 体系架构上每个IRQ相关的中断号列表;
  • /proc/iomem 每个物理设备上的记忆体(RAM或者ROM)在系统内存中的映射信息;
  • /proc/kmsg 此文件用来保存由内核输出的信息,通常由/sbin/klogd或/bin/dmesg等程序使用,不要试图使用查看命令打开此文件;
  • /proc/meminfo 系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值;
  • /proc/modules当前装入内核的所有模块名称列表,可以由lsmod命令使用,也可以直接查看,第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(live:已经装入;loading:正在装入;unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量;/proc/partilions块设备每个分区的主设备号(minor)和次设备号(minor)等信息,同时包括每个分区所包含的块(block)数目
  • /proc/version当前系统运行的内核版本号
  • /proc/uptime:系统上次启动以来的运行时间,第一个数字表示系统运行时间,第二个数字表示系统空闲时间,单位是秒

创建/proc节点

  • 首先是创建proc_dir_entry结构体
    内核使用proc_dir_entry结构体来描述/proc文件系统中的一个目录或则文件节点
/*
 * This is not completely implemented yet. The idea is to
 * create an in-memory tree (like the actual /proc filesystem
 * tree) of these proc_dir_entries, so that we can dynamically
 * add new files to /proc.
 *
 * parent/subdir are used for the directory structure (every /proc file has a
 * parent, but "subdir" is empty for all non-directory entries).
 * subdir_node is used to build the rb tree "subdir" of the parent.
 */
struct proc_dir_entry {
	/*
	 * number of callers into module in progress;
	 * negative -> it's going away RSN
	 */
	atomic_t in_use;               /* 调用模块的进程数;负数表示正在卸载 */
	refcount_t refcnt;
	struct list_head pde_openers;	/* who did ->open, but not ->release */
	                                /* 打开文件的进程链表头 */
	/* protects ->pde_openers and all struct pde_opener instances */
	spinlock_t pde_unload_lock;               /* 卸载锁 */
	struct completion *pde_unload_completion;   /* 卸载完成信号量指针 */
	const struct inode_operations *proc_iops;   /* inode 操作函数集指针 */
	const struct file_operations *proc_fops;    /* 文件操作函数集指针 */
	const struct dentry_operations *proc_dops;
	union {
		const struct seq_operations *seq_ops;  /* 序列化操作函数集指针 */
		int (*single_show)(struct seq_file *, void *);
	};
	proc_write_t write;
	void *data;             /* 自定义数据指针 */
	unsigned int state_size;
	unsigned int low_ino;  /* 低位 inode 编号 */
	nlink_t nlink;         /* 链接计数 */
	kuid_t uid;            /* 用户 ID  */
	kgid_t gid;            /* 组 ID  */
	loff_t size;           /* 文件大小 */
	struct proc_dir_entry *parent;  /* 目录项指针 */
	struct rb_root subdir;          /* 子目录 */
	struct rb_node subdir_node;
	char *name;      /* 名称 */
	umode_t mode;    /* 文件访问权限 */
	u8 namelen;      /* 名称长度 */
	char inline_name[];
} __randomize_layout;
  • 其次初始化文件操作集
    使用file_operations结构体实现proc文件读写,实现file_operations里面的read、write即可实现proc文件读写
  • 然后使用proc_create创建文件节点
    内核里面提供了proc_create函数来创建文件节点
    其调用函数流程如下
    【linux API分析】proc_create() 及初识proc_第2张图片

proc_create()函数

/**********************************************************************
*  @ name:节点名
*  @ mode:权限位
*  @ parent: 父目录
*  @ proc_fops:文件操作结构体
**********************************************************************/
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);
}

proc_create_data()函数

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)
{
	struct proc_dir_entry *p;

	BUG_ON(proc_fops == NULL);

	p = proc_create_reg(name, mode, &parent, data);
	if (!p)
		return NULL;
	p->proc_fops = proc_fops;
	return proc_register(parent, p);
}
/******************************************************************************
* “assert”用于在代码中进行断言,如果条件为真(非零),则程序会继续执行,如果条件为假(0)
* 则程序会引发一个AssertionError异常,表明程序的某些部分没有按照预期工作
*******************************************************************************/
#define BUG_ON(__BUG_ON_cond) assert(!(__BUG_ON_cond))

proc_create_reg()函数

struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode,
		struct proc_dir_entry **parent, void *data)
{
	struct proc_dir_entry *p;

	if ((mode & S_IFMT) == 0)
		mode |= S_IFREG;
	if ((mode & S_IALLUGO) == 0)
		mode |= S_IRUGO;
	if (WARN_ON_ONCE(!S_ISREG(mode)))
		return NULL;

	p = __proc_create(parent, name, mode, 1);
	if (p) {
		p->proc_iops = &proc_file_inode_operations;
		p->data = data;
	}
	return p;
}

__proc_create()函数

static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
					  const char *name,
					  umode_t mode,
					  nlink_t nlink)
{
	struct proc_dir_entry *ent = NULL;
	const char *fn;
	struct qstr qstr;

	if (xlate_proc_name(name, parent, &fn) != 0)
		goto out;
	qstr.name = fn;
	qstr.len = strlen(fn);
	if (qstr.len == 0 || qstr.len >= 256) {
		WARN(1, "name len %u\n", qstr.len);
		return NULL;
	}
	if (qstr.len == 1 && fn[0] == '.') {
		WARN(1, "name '.'\n");
		return NULL;
	}
	if (qstr.len == 2 && fn[0] == '.' && fn[1] == '.') {
		WARN(1, "name '..'\n");
		return NULL;
	}
	if (*parent == &proc_root && name_to_int(&qstr) != ~0U) {
		WARN(1, "create '/proc/%s' by hand\n", qstr.name);
		return NULL;
	}
	if (is_empty_pde(*parent)) {
		WARN(1, "attempt to add to permanently empty directory");
		return NULL;
	}

	ent = kmem_cache_zalloc(proc_dir_entry_cache, GFP_KERNEL);
	if (!ent)
		goto out;

	if (qstr.len + 1 <= SIZEOF_PDE_INLINE_NAME) {
		ent->name = ent->inline_name;
	} else {
		ent->name = kmalloc(qstr.len + 1, GFP_KERNEL);
		if (!ent->name) {
			pde_free(ent);
			return NULL;
		}
	}

	memcpy(ent->name, fn, qstr.len + 1);
	ent->namelen = qstr.len;
	ent->mode = mode;
	ent->nlink = nlink;
	ent->subdir = RB_ROOT;
	refcount_set(&ent->refcnt, 1);
	spin_lock_init(&ent->pde_unload_lock);
	INIT_LIST_HEAD(&ent->pde_openers);
	proc_set_user(ent, (*parent)->uid, (*parent)->gid);

	ent->proc_dops = &proc_misc_dentry_ops;

out:
	return ent;
}

__xlate_proc_name()函数

/*
 * This function parses a name such as "tty/driver/serial", and
 * returns the struct proc_dir_entry for "/proc/tty/driver", and
 * returns "serial" in residual.
 */
static int __xlate_proc_name(const char *name, struct proc_dir_entry **ret,
			     const char **residual)
{
	const char     		*cp = name, *next;
	struct proc_dir_entry	*de;
	unsigned int		len;

	de = *ret;
	if (!de)
		de = &proc_root;

	while (1) {
		next = strchr(cp, '/');
		if (!next)
			break;

		len = next - cp;
		de = pde_subdir_find(de, cp, len);
		if (!de) {
			WARN(1, "name '%s'\n", name);
			return -ENOENT;
		}
		cp += len + 1;
	}
	*residual = cp;
	*ret = de;
	return 0;
}

代码解读:de = &proc_root指定初始父目录为/proc
strchr()用于检查待创建文件的文件名是否含有/,若有,则更改父目录,直到无为止,最后剩下的名字不含有/,比如tty/driver/serial,经过while循环后,其父目录为/proc/tty/driver,文件名为serial。
这段代码查找目录的方式:利用结构体来查找该目录的位置(在已知成员变量的位置后,根据结构体成员变量在结构体中的偏移量,推出结构体位置),以及先判断文件名字长度,长度一致再使用memcmp()函数来判断名字是否一致

proc代码实例

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>

/* 定义一个proc_dir_entry结构体类型变量*/
struct proc_dir_entry *hello_proc = NULL;
/* 定义一个全局数据,用来保存用户空间返回的数据 */
static char hello_data[20] = {};

/* 如果使用cat此节点,则传入的count为4K,直到读取的数据大小为4K,也就是直到此函数返回0
   当此函数返回0时,读取到的内容是不显示的。
 */
static ssize_t hello_proc_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
	int ret = 0;
	/* 首先清空用户空间的user_buf地址的内容,有可能显示杂乱信息 */
    if (clear_user(user_buf, count)) {
        printk(KERN_ERR "clear error\n");
        return -EIO;
    }

	/* 从hello_data数组中读取数据到用户空间user_buf,读取的长度应该是字符串的大小 */
	ret = simple_read_from_buffer(user_buf, count, ppos, hello_data, strlen(hello_data));

    return ret;
}

/*
  用户空间使用echo往此节点写入数据,只有要写入的数据写完之后,也就是返回count是,此函数此不会被调用
*/
static ssize_t hello_proc_write(struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
	int ret;
    printk("hello_proc_write:count is %d.\n",count);
	/* 写入数据之前,将数组清空 */
	memset(hello_data,0,sizeof(hello_data));
	/* 将用户空间写入的数据保存到数据中 */
	ret = simple_write_to_buffer(hello_data, sizeof(hello_data),ppos,user_buf,count);

	printk("hello_proc_write:ret is %d.\n",ret);
	printk("hello_proc_write:user_buf is %s",hello_data);
    /* 返回用户空间写入字符串的大小 */
	return count;
}

/* 定义一个file_operations结构体变量 */
static const struct file_operations hello_proc_fops = {
    .owner      = THIS_MODULE,
	.read		= hello_proc_read,	//使用cat时的回调函数
	.write      = hello_proc_write,	//使用echo时的回调函数
};

/* 驱动入口函数 */
static int __init proc_test_init(void)
{
	/* 调用proc_create()函数创建"hello_proc"文件 */
    hello_proc = proc_create("hello_proc", S_IRUGO | S_IWUGO,NULL,&hello_proc_fops);
    return 0;
}

/* 驱动出口函数 */
static void __exit proc_test_exit(void)
{
	/* 删除此文件 */
	if(hello_proc)
		remove_proc_entry("hello_proc", NULL);
}

module_init(proc_test_init);
module_exit(proc_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("proc filesystem test"); 

你可能感兴趣的:(linux,api,linux,linux)