일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- allocator
- slub
- Kernel
- page
- kmalloc
- pmap
- spinlock
- blk-mq
- Network
- Linux
- devicedriver
- slab
- strex
- lruvec
- Apache
- vm_area_struct
- commit
- proc
- memory
- BLOCK
- multiqueue
- kafka
- NDK
- fastpath
- 카프카
- buddy_system
- slowpath
- mm_struct
- Android
- vmalloc
- Today
- Total
Art of Pr0gr4m
[Linux Kernel 5] proc & seq_file 본문
procfs는 Process File System을 줄인 것으로, Processes as Files의 의미이다
커널 및 디바이스 정보 (시스템 정보)를 유저 스페이스에 제공하기 위해 사용된다
ls /proc 으로 리스팅 해보면 다음과 같이 여러 파일 및 디렉토리가 있다
각 파일과 디렉토리가 의미하는건 다음 링크들을 참고한다
https://ko.wikipedia.org/wiki/Procfs
http://www.tldp.org/LDP/sag/html/proc-fs.html
http://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html
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)
5. 실행 결과 (seq_operations)
실행 결과와 같이 정상적으로 입출력이 되는것을 볼 수 있다
또한, single_open을 사용할 경우 simple_show가 한번만 출력되는데
(위의 dmesg에서는 출력 자체를 여러번 했기 때문에 여러번 호출되었다)
seq_operations을 사용할 경우 start -> show -> next -> show -> ...가 계속 반복된걸 볼 수 있다
'IT > Linux Kernel' 카테고리의 다른 글
[Linux Kernel 5] Block Device Driver Basic Concept (2) | 2020.05.04 |
---|---|
[Linux Kernel 5] Kernel Timer (jiffies & ktime) (0) | 2020.05.02 |
[Linux Kernel 5] Crypto Device Driver (0) | 2020.04.30 |
[Linux Kernel 5] Character Device Driver IOCTL (0) | 2020.04.30 |
[Linux Kernel 5] Character Device Driver (3) | 2020.04.30 |