Art of Pr0gr4m

[Linux Kernel 5] proc & seq_file 본문

IT/Linux Kernel

[Linux Kernel 5] proc & seq_file

pr0gr4m 2020. 5. 2. 18:11

procfs는 Process File System을 줄인 것으로, Processes as Files의 의미이다

커널 및 디바이스 정보 (시스템 정보)를 유저 스페이스에 제공하기 위해 사용된다

 

 ls /proc 으로 리스팅 해보면 다음과 같이 여러 파일 및 디렉토리가 있다

 

ls /proc 결과

각 파일과 디렉토리가 의미하는건 다음 링크들을 참고한다

 

https://ko.wikipedia.org/wiki/Procfs

 

procfs - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 proc 파일시스템 (procfs)은 유닉스 계열 운영 체제에서 프로세스와 다른 시스템 정보를 계층적 파일 구조 같은 형식으로 보여주는 특별한 파일시스템으로서, 전통적인 트레이싱 방식이나 커널 메모리로의 간접적인 접근 보다는 더 편리하고 표준적인 방식인 동적으로 커널이 소유하는 프로세스 데이터에 접근하는 방식을 제공한다. 일반적으로 이것은 부트 타임에 /proc 라는 이름의 마운트 포인트에

ko.wikipedia.org

http://www.tldp.org/LDP/sag/html/proc-fs.html

 

The /proc filesystem

3.7. The /proc filesystem The /proc filesystem contains a illusionary filesystem. It does not exist on a disk. Instead, the kernel creates it in memory. It is used to provide information about the system (originally about processes, hence the name). Some o

www.tldp.org

http://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html

 

/proc

Again, super block structures are allocated by the kernel, but not freed. The file super-max contains the maximum number of super block handlers, where super-nr shows the number of currently allocated ones. Every mounted file system needs a super block,

tldp.org

 

 

1. procfs interface

 

procfs 인터페이스는 구 버전 커널 (2.6 버전)과 비교하면 꽤나 변경이 있었다

구버전 인터페이스는 링크를 참고한다

 

다음은 현재 커널 5버전에서 procfs의 인터페이스를 최대한 간단하게 사용한 예제이다

 

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
 
static int simple_proc_show(struct seq_file *m, void *v)
{
	seq_printf(m, "Hello proc\n");
	return 0;
}
 
static int simple_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, simple_proc_show, NULL);
}
 
static const struct proc_ops simple_proc_fops = {
	.proc_open = simple_proc_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release
};
 
static int __init simple_init(void)
{
	proc_create("simple", 0, NULL, &simple_proc_fops);
	return 0;
}
 
static void __exit simple_exit(void)
{
	remove_proc_entry("simple", NULL);
}
 
module_init(simple_init);
module_exit(simple_exit);
 
MODULE_LICENSE("GPL");

 

procfs 인터페이스의 핵심은 proc_create와 proc_ops이다

 

struct proc_ops {
	int	(*proc_open)(struct inode *, struct file *);
	ssize_t	(*proc_read)(struct file *, char __user *, size_t, loff_t *);
	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);
};
 
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops);
 
 

 

file_operations 구조체와 유사하게 proc_ops 구조체에 원하는 오퍼레이션을 등록한 후

proc_create로 /proc 디렉토리 하위의 엔트리를 생성한다

 

proc_create의 인자는 name이 엔트리 이름, mode가 권한, parent가 부모 디렉토리, proc_ops가 등록할 오퍼레이션이다

 

 

 

2. seq_file 인터페이스

 

procfs 인터페이스는 proc_ops에 seq_file (시퀀스 파일) 인터페이스를 연결하여 사용한다

seq_file 인터페이스를 사용하는 방법은 seq_operations를 직접 구현하여 등록하는 것과

위 예제처럼 single_open / single_release을 사용하는 방법이 있다

 

struct seq_file {
	char *buf;
	size_t size;
	size_t from;
	size_t count;
	size_t pad_until;
	loff_t index;
	loff_t read_pos;
	u64 version;
	struct mutex lock;
	const struct seq_operations *op;
	int poll_event;
	const struct file *file;
	void *private;
};
 
struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};
 
int single_open(struct file *, int (*)(struct seq_file *, void *), void *);
int single_release(struct inode *, struct file *);

 

seq_operations를 이용하는 경우는 seq_open에 seq_operations 구조체를 등록하는데

해당 구조체에 등록한 함수가 start -> show -> next -> show -> next -> show -> ... -> stop 순으로 반복 호출된다

 

single_open을 사용하는 경우엔 두 번째 인자로 show 오퍼레이션 엔트리를 등록하면

해당 함수를 한번만 호출하여 처리한다

 

 

 

3. proc_fs 예제 소스

 

proc_fs.h

#ifndef __LIST_PROC_H__
#define __LIST_PROC_H__
 
#include <linux/mutex.h>
#include <linux/list.h>
 
#define USE_SINGLE_OPEN
 
struct m_info {
	int a;
	int b;
	struct list_head list;
};
 
extern struct mutex m_lock;
extern struct list_head m_list;
 
#endif

 

proc_fs.c

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/version.h>
#include <linux/uaccess.h>
#include "proc_fs.h"
 
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("pr0gr4m");
MODULE_DESCRIPTION("proc list add driver");
 
DEFINE_MUTEX(m_lock);
LIST_HEAD(m_list);
 
static int add_data(int a, int b)
{
	struct m_info *info;
	printk("%s %d, %d\n", __func__, a, b);
 
	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;
 
	INIT_LIST_HEAD(&info->list);
	info->a = a;
	info->b = b;
 
	mutex_lock(&m_lock);
	list_add(&info->list, &m_list);
	mutex_unlock(&m_lock);
 
	return 0;
}
 
static int add_sample_data(void)
{
	if (add_data(10, 20))
		return -ENOMEM;
	if (add_data(30, 40))
		return -ENOMEM;
	return 0;
}
 
static int remove_sample_data(void)
{
	struct m_info *tmp;
	struct list_head *node, *q;
	list_for_each_safe(node, q, &m_list) {
		tmp = list_entry(node, struct m_info, list);
		list_del(node);
		kfree(tmp);
	}
 
	return 0;
}
 
 
#ifdef USE_SINGLE_OPEN
static int simple_show(struct seq_file *s, void *v)
{
	struct m_info *info;
	printk("%s", __func__);
	list_for_each_entry(info, &m_list, list)
		seq_printf(s, "%d + %d = %d\n", info->a, info->b,
				info->a + info->b);
	return 0;
}
#else
static void *seq_start(struct seq_file *s, loff_t *pos)
{
	printk("%s", __func__);
	mutex_lock(&m_lock);
	s->private = "";
 
	return seq_list_start(&m_list, *pos);
}
 
static void *seq_next(struct seq_file *s, void *v, loff_t *pos)
{
	printk("%s", __func__);
	s->private = "\n";
 
	return seq_list_next(v, &m_list, pos);
}
 
static void seq_stop(struct seq_file *s, void *v)
{
	mutex_unlock(&m_lock);
	printk("%s", __func__);
}
 
static int seq_show(struct seq_file *m, void *v)
{
	struct m_info *info = list_entry(v, struct m_info, list);
	printk("%s", __func__);
	seq_printf(m, "%d + %d = %d\n", info->a, info->b, info->a + info->b);
	return 0;
}
 
static const struct seq_operations seq_ops = {
	.start = seq_start,
	.next = seq_next,
	.stop = seq_stop,
	.show = seq_show
};
#endif
 
static int proc_open(struct inode *inode, struct file *file)
{
#ifdef USE_SINGLE_OPEN
	return single_open(file, simple_show, NULL);
#else
	return seq_open(file, &seq_ops);
#endif
}
 
static ssize_t proc_write(struct file *seq, const char __user *data, 
		size_t len, loff_t *off)
{
	char buf[128];
	int a, b;
	static int finished = 0;
 
	if (finished) {
		printk("m_write end\n");
		finished = 0;
		return 0;
	}
	finished = 1;
 
	if (copy_from_user(buf, data, len)) {
		printk(KERN_ERR "copy from user error");
		return -EFAULT;
	}
 
	sscanf(buf, "%d %d", &a, &b);
 
	if (add_data(a, b)) {
		printk(KERN_ERR "add_data in m_wrtie\n");
		return -EFAULT;
	}
 
	return len;
}
 
static const struct proc_ops proc_ops = {
	.proc_open = proc_open,
	.proc_read = seq_read,
	.proc_write = proc_write,
	.proc_lseek = seq_lseek,
	.proc_release = seq_release
};
 
#define M_DIR	"pr0gr4m-dir"
#define M_FILE	"pr0gr4m"
 
static struct proc_dir_entry *proc_dir = NULL;
static struct proc_dir_entry *proc_file = NULL;
 
int proc_init(void)
{
	if ((proc_dir = proc_mkdir(M_DIR, NULL)) == NULL) {
		printk(KERN_ERR "Unable to create /proc/%s\n", M_DIR);
		return -1;
	}
 
	if ((proc_file = proc_create(M_FILE, 0666, proc_dir, &proc_ops)) == NULL) {
		printk(KERN_ERR "Unable to create /proc/%s/%s\n", M_DIR, M_FILE);
		remove_proc_entry(M_DIR, NULL);
		return -1;
	}
 
	printk(KERN_INFO "Created /proc/%s/%s\n", M_DIR, M_FILE);
	return 0;
}
 
void proc_exit(void)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)
	remove_proc_entry(M_FILE, proc_dir);
	remove_proc_entry(M_DIR,, NULL);
#else
	remove_proc_subtree(M_DIR, NULL);
#endif
 
	proc_remove(proc_file);
	proc_remove(proc_dir);
 
	printk(KERN_INFO "Removed /proc/%s/%s\n", M_DIR, M_FILE);
}
 
static int __init proc_dev_init(void)
{
	if (add_sample_data()) {
		printk(KERN_ERR "add_sample_data() failed\n");
		return -ENOMEM;
	}
	return proc_init();
}
 
static void __exit proc_dev_exit(void)
{
	remove_sample_data();
	proc_exit();
 
	return;
}
 
module_init(proc_dev_init);
module_exit(proc_dev_exit);

 

해당 예제에서 커널 자료구조인 list와 커널 동기화 매커니즘인 mutex를 사용하였다

list와 mutex 관련 내용은 추후 포스팅 예정이므로 설명은 생략한다

 

add_data 함수는 리스트에 데이터를 추가한다

 

매크로를 이용하여 single_open을 사용하는 경우와 seq_operations를 사용하는 경우를 전부 보이고 있다

proc_open 함수에서 single_open을 사용하는 경우엔 single_open 함수에 simple_show 함수를 등록한다

seq_operations를 사용하는 경우엔 seq_operations 구조체에 오퍼레이션들을 등록한 후,

seq_open에 seq_operations 구조체를 등록한다

 

proc_ops 구조체에는 작성한 proc_open 함수와 proc_write 함수를 등록한다

proc_write 함수는 생성된 엔트리에 데이터를 쓸 때 호출될 것이고,

proc_open 함수는 생성된 엔트리를 열어서 읽을 때 호출될 것이다

 

proc_mkdir 함수로 /proc 디렉토리 아래에 디렉토리를 생성하고, proc_create 함수로 엔트리를 생성한다

 

 

 

 

4. 실행 결과 (single_open)

 

/proc/pr0gr4m-dir/pr0gr4m 입출력 결과
dmesg 결과

 

 

 

5. 실행 결과 (seq_operations)

 

dmesg 결과

 

 

실행 결과와 같이 정상적으로 입출력이 되는것을 볼 수 있다

또한, single_open을 사용할 경우 simple_show가 한번만 출력되는데

(위의 dmesg에서는 출력 자체를 여러번 했기 때문에 여러번 호출되었다)

seq_operations을 사용할 경우 start -> show -> next -> show -> ...가 계속 반복된걸 볼 수 있다