수정한 커널 코드가 특정 조건에서만 동작하도록 도와주는 간단한 debugfs 드라이버 코드에 대해 분석 및 내 리눅스에서 정상적으로 동작하도록 수정한다.
최종 동작은 /sys/kernel/debug/rpi_debu/val 라는 파일에 값을 생성하고 그것을 확인하는것인데, 이 파일을 어떻게 생성하고 값을 수정할 수 있는지 코드를 분석해봤음.
DEFINE_SIMPLE_ATTRIBUTE 라는 매크로가 굉장히 중요한 역할을 수행하는거 같음.
include/linux/fs.h 에서 DEFINE_SIMPLE_ATTRBUTE 는 다음과 같다.
#define DEFINE_SIMPLE_ATTRIBUTE(__fops, __get, __set, __fmt) \
DEFINE_SIMPLE_ATTRIBUTE_XSIGNED(__fops, __get, __set, __fmt, true)
DEFINE_SIMPLE_ATTRIBUTE_XSIGNED의 주석은 다음과 같다.
*
3601 * simple attribute files
3602 *
3603 * These attributes behave similar to those in sysfs:
3604 *
3605 * Writing to an attribute immediately sets a value, an open file can be
3606 * written to multiple times.
3607 *
3608 * Reading from an attribute creates a buffer from the value that might ge t
3609 * read with multiple read calls. When the attribute has been read
3610 * completely, no further read calls are possible until the file is opened
3611 * again.
3612 *
simple attribute file 은 sysfs의 attribute와 비슷한 역할을 수행한다? 그게 무슨 말일까?
Documentation/filesystems/sysfs.rst로 가보자
18 sysfs is a ram-based filesystem initially based on ramfs. It provides
19 a means to export kernel data structures, their attributes, and the
20 linkages between them to userspace
........
30 sysfs is always compiled in if CONFIG_SYSFS is defined. You can access
31 it by doing::
32
33 mount -t sysfs sysfs /sys
...
39 For every kobject that is registered with the system, a directory is
40 created for it in sysfs
...
54 Attributes
55 ~~~~~~~~~~
56
57 Attributes can be exported for kobjects in the form of regular files in
58 the filesystem. Sysfs forwards file I/O operations to methods defined
59 for the attributes, providing a means to read and write kernel
60 attributes.
즉 sysfs 라는 virtual file system은 userspace에서 kernel object에 접근할 수 있게 해주는 가상의 파일 시스템으로서 /sys 디렉토리에 mount되어있다고 보면 되겠다. subdirectory들은 커널 내부의 subsystem을 의미하며 각 kobject들을 representing 하는 file 형식의 attribute가 존재한다.
Writing to attribute file: kernel object의 attribute 를 수정할 수 있다.
Reading from attribute file: attribute의 정보를 buffer에 저장하여 읽어올 수 있다.
여기까지 왔다면 attr 라는 파일을 통해 userspace 에서 kernel object 로 접근할 수 있다는것을 알고, 이것이 /sys directory에 mount되어있다는것을 알았다.
그렇다면 DEFINE_SIMPLE_ATTRIBUTE가 정확히 어떤 동작을 수행하는걸까? 이 macro 의 코드를 살펴보자
#define DEFINE_SIMPLE_ATTRIBUTE_XSIGNED(__fops, __get, __set, __fmt, __is_signed) \
static int __fops ## _open(struct inode *inode, struct file *file) \
{ \
__simple_attr_check_format(__fmt, 0ull); \
return simple_attr_open(inode, file, __get, __set, __fmt); \
} \
static const struct file_operations __fops = { \
.owner = THIS_MODULE, \
.open = __fops ## _open, \
.release = simple_attr_release, \
.read = simple_attr_read, \
.write = (__is_signed) ? simple_attr_write_signed : simple_attr_write, \
.llseek = generic_file_llseek, \
}
어떤 function 을 정의하고, file operation struct의 open 필드에 그것을 대입하고, read,write,release 등등에 대한 field에 각각의 function들을 대입하는 macro이다.
딱보면 감이 오겠지만, fops 는 file operation 구조체로서 어떤 파일에 R/W을 수행할때 정확히 어떤 동작을 수행할것인지를 설정하는 구조체이다.
모든 파일에 접근하기전에는 그 파일을 open() 하는 과정이 필요하다. 그렇다면 여기 fops 에 정의된대로 파일에 open() 시스템콜을 수행하면 우리가 정의한 _fops_open() function 이 호출된다. 즉, format checking 후 simple_attr_open(inode, file, get, set, fmt) function을 호출하는것이다.
simple_attr_open() 은 무엇을 할까?
int simple_attr_open(struct inode *inode, struct file *file,
int (*get)(void *, u64 *), int (*set)(void *, u64),
const char *fmt)
{
struct simple_attr *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr)
return -ENOMEM;
attr->get = get;
attr->set = set;
attr->data = inode->i_private;
attr->fmt = fmt;
mutex_init(&attr->mutex);
file->private_data = attr;
return nonseekable_open(inode, file);
}
simple_attr struct를 하나 만들고, argument 로 field 들을 하나씩 넣는다. 그 후, 지정된 file의 private_data field에 해당 attr를 넣는다.
즉, 이 fops file operation 으로 만들어진 파일들은 open() 이 호출될때 커스텀한 get과 set function이 설정된 attr가 그 파일에 저장되게 된다.
잠시만, 그렇다면 read/write 할때 이 attr의 get과 set값을 확인하고 그 function 을 수행하도록 하는 행동 또한 file operation 에 정의되어야 한다. 그게 아래의 이부분이다.
.read = simple_attr_read, \
.write = (__is_signed) ? simple_attr_write_signed : simple_attr_write, \
즉 read할때 simple_attr_read 으로, write할때 simple_attr_write 으로 하라는것이다.
여기까지 왔다면
를 알았다. 그렇다면 이제 이 개념들을 사용하여 kernel object가 연결된 file 을 만들것이다.
debugfs는 /sys/kernel/debug/ 디렉토리에 마운트되어있으며, debugging 에 관련된 어느 정보건 개발자가 원하는대로 마음껏 활용할 수 있다.
파일과 디렉토리를 만드는데에는 debug에 관련된 아래 2개의 system call 이 사용된다.
/modules/debugfs.c
1 #include <linux/debugfs.h>
2 #include <linux/kernel.h>
3 #include <linux/module.h>
4
5 MODULE_LICENSE("GPL");
6
7 static u32 number = 0;
8 static struct dentry *dir = 0;
9
10 static int get_debug_number(void* data, u64* val) {
11
12 *val = number;
13
14 return 0;
15 }
16
17 static int set_debug_number(void* data, u64 val) {
18 number = val;
19 return 0;
20 }
21
22
23 DEFINE_SIMPLE_ATTRIBUTE(debug_fops, get_debug_number, set_debug_number, "%llu\n");
24
25 int init_module(void) {
26
27 struct dentry* junk;
28
29 dir = debugfs_create_dir("debug_number", NULL);
30
31 if(!dir) {
32 printk(KERN_ALERT "debugfs_create_dir: failed to create debugfs directory");
33 }
34
35 junk = debugfs_create_file("deubgfs",0444,dir,&number,&debug_fops);
36
37 if(!junk) {
38 printk(KERN_ALERT "debugfs_create_file: failed to create debugfs file");
39 }
40
41 return 0;
/modules/Makefile
1 obj-m := debugfs.o
2 default:
3 make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
여기서 /lib/modules/kernel/build 는 kernel 의 source code tree 와 symbolic linking 되어있다.
즉, /root/linux-source-6.1 디렉토리에서 make을 수행할것인데, M= 옵션으로 현재 디렉토리(modules) 의 makefile 내용을 kernel의 Makefile 내용에 추가하겠다는것이다.
modules 옵션 $PWD 아래에 있는 external module을 build하라는것임.
아래 명령어로 module insertion 가능
sudo insmod /modules/debugfs.ko