debugfs 드라이버

1231·2025년 3월 9일

수정한 커널 코드가 특정 조건에서만 동작하도록 도와주는 간단한 debugfs 드라이버 코드에 대해 분석 및 내 리눅스에서 정상적으로 동작하도록 수정한다.

최종 동작은 /sys/kernel/debug/rpi_debu/val 라는 파일에 값을 생성하고 그것을 확인하는것인데, 이 파일을 어떻게 생성하고 값을 수정할 수 있는지 코드를 분석해봤음.

DEFINE_SIMPLE_ATTRIBUTE macro 분석

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 으로 하라는것이다.

debugfs directory/file 만들기

여기까지 왔다면

  • kernel object가 파일 형태로 /sys 파일 아래에서 존재할 수 있다는 점
  • DEFINE_SIMPLE_ATTRIBUTE 매크로가 특정 행동을 정의하는 구조체인 file operation 을 만든다는 점
  • 특정 fops 가 연결되어있는 파일이 open, read, write 될때 어떤 과정이 이루어지는지(simple_attr_open/read/write)

를 알았다. 그렇다면 이제 이 개념들을 사용하여 kernel object가 연결된 file 을 만들것이다.

debugfs는 /sys/kernel/debug/ 디렉토리에 마운트되어있으며, debugging 에 관련된 어느 정보건 개발자가 원하는대로 마음껏 활용할 수 있다.
파일과 디렉토리를 만드는데에는 debug에 관련된 아래 2개의 system call 이 사용된다.

  • debugfs_create_directory()
  • debugfs_create_file()

/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 

0개의 댓글