2007년 11월 22일 목요일

proc, module programing



<< proc file system >>

proc file system은 파일과 프로세스를 통합하여 관리한다.
/proc 디렉토리에 가보면 프로세스나 하드웨어 정보를 담고있는 파일들을 볼 수가 있다.
이 파일들은 실제로 하드디스크 공간을 차지하지는 않으며, 사용자가 접근을 할 때 커널이 그 내용을 동적으로 만들어준다.

즉, user가 proc file system의 내용을 사용하려고 호출을 하면 커널이 그에 맞는 등록함수를 찾아서 사용할 수 있는 것있다.
그러므로 실제 data가 있는 것이 아니고 이 file system을 거쳐서 등록함수를 찾아내는 것이다. 이 등록함수들을 통하여 커널에 접근을 할 수 있으며, 이 함수들을 module이라고 한다.
결국, proc file system은 매개체의 역할을 하는 한마디로 trigger라고 할 수 있다.


그럼, 지금부터 module을 만들어서 proc을 사용해보도록 하자.



<< Module Programing >>

* 주의할 점 *
module programing은 kernel단의 programing이므로 자칫 잘못하다가는 시스템이 죽을 수 있으며, programing을 할 때 평소 사용하던 standard library는 사용할 수 없다.


1. 모듈 init 함수와 exit 함수.

module program에 꼭 필요한 것이 있는데 바로 init함수와 exit함수이다.
init함수는 module이 생성될 때 호출되는 것이며, exit함수는 module이 사라질 때 호출되는 것으로 C++의 생성자/소멸자와 비슷한 개념이다.

함수의 이름은 다음과 같이 지정해주어야만, init함수/exit함수로 인지할 수 있다.

//init 함수
int init_module(void)
{
  
  // ... 필요한 일들을 수행
  
  return 0;
}

//exit 함수
void cleanup_module(void)
{
  // ... 필요한 일들을 수행
}

만약, 원하는 함수이름으로 지정을 하고 싶다면 원하는 이름으로 하되, 그 함수가 init함수/exit함수라는 것을 알려줄 수 있어야 한다.

init함수/exit함수를 지정해주는 매크로는 다음과 같다.
만약 init함수를 int myproc_init(void) 라고 선언하였고, exit함수를 void myproc_exit(void)라고 선언하였다면,

module_init(myproc_init);
module_exit(myproc_exit);


.. 이라고 사용한다.

init함수/exit함수만 알아도 hello world같은 쉬운 프로그래밍을 구현할 수 있다.


2. directory 만들기

내가 만든 모듈을 proc file system에 연결시켜서 사용하기 위해서는 proc file system에 directory가 필요하다.
이 directory로 접근을 하는 명령을 사용한다면 현재 programing하는 module을 가져올 것이다.

보통, directory는 init함수 안에서 생성하게 된다.

directory를 만들기 위해서는 proc_dir_entry라는 구조체가 필요하다.
전역변수로 struct proc_dir_entry형 변수를 하나 pointer로 선언한 후 함수를 호출한다.

static struct proc_dir_entry *dir; //전역변수로 선언
dir = proc_mkdir(MODULE_NAME, NULL);

여기서 말하는 MODULE_NAME은 스스로 지정해주어야 하는 문자열이며, 이 이름으로 directory를 생성한다.
directory생성에 실패할 경우 dir에 NULL값이 retrun되므로 확인하여 에러처리 할 수 있다.


3. read/write 콜백 함수

user가 read나 write를 위하여 module에 접근할 경우 read를 담당하는 함수와 write를 담당하는 함수가 필요하다.
사용자의 요구를 처리하기 위한 함수로 "cat"같은 명령어로 접근할 경우에는 read를 담당하는 콜백함수가 호출되고 "echo"같은 명령어로 접근할 경우에는 write를 담당하는 콜백함수가 호출된다.

그러니까, 결국은 콜백함수를 만들어 줘야 한다는 것이다.
읽기함수나 쓰기 함수의 이름은 어떤 것으로 해주어도 상관이 없지만 형식은 지켜주어야 한다.

읽기/쓰기 call back 함수의 형식은 다음과 같다.

int read_func(char* page, char** start, off_t off, int count, int* eof, void* data);
int write_func(struct file* file, const char *buffer, unsigned long count, void *data);

읽기 함수에서는 읽을 내용을 page에 읽어오고, 쓰기 함수에서는 buffer에 사용자가 쓴 데이터를 가져온다.
이러한 형식으로 선언된 call back함수에서는 read/write로 접근하였을 때 해주어야 할일을 구현하면 된다.

이 call back함수들을 위한 entry를 생성하고 등록을 시켜주는 일은 init함수에서 한다.

우선, READ를 위한 것 부터 보면 다음과 같다.

static struct proc_dir_entry *read_file; //전역변수로 선언
read_file = create_proc_read_entry("read", 0444, "MyProc", myproc_read, NULL);

init함수에서 create_proc_read_entry함수를 호출하여 entry생성과 등록을 시켜준다. 읽기 전용으로 "read"파일을 MyProc 디렉토리 안에 만드는 것이다. 이 때 MyProc은 위에서 생성한 directory (MODULE_NAME)와 이름이 같아야 한다. 0444는 접근 권한이며 myproc_read라는 함수를 call back함수로 등록시켜준다.
create_proc_read_entry라는 함수는 wrapper함수이다. 이 함수 안에서도 결국 create_proc_entry를 호출을 하여 사용하지만 읽기 전용 proc파일을 생성하기 쉽게 하기 위해서 사용한다.
이 wrapper함수도 위에 directory 생성 함수와 마찬가지로 read_file로 return된 값이 NULL이라면 entry생성에 실패한 것이므로 에러처리를 해줄 수 있다.

다음은 WRITE를 위한 함수를 보자.

static struct proc_dir_entry *write_file; //전역변수로 선언
write_file = create_proc_entry("write", 0644, "MyProc");

init함수에서 create_proc_entry를 호출하여 entry를 생성하고 등록시킨다.
읽기/쓰기 전용으로 "write"파일을 MyProc디렉토리 안에 만들게 된다.
create_proc_read_entry 안에서도 이 함수를 호출하게 되나 wrapper함수를 사용하면 더 좋은 점은 읽기전용에 대한 설정들을 자동으로 해준다는 것이다.
create_proc_entry를 호출하게 되면 proc_dir_entry 구조체에 있는 값들을 설정을 해주어야 한다. wrapper함수를 사용하면 call back함수를 자동으로 연결시켜주지만, 이 함수는 그렇지 못하므로 그 연결을 해주기 위한 설정이 필요한 것이다.

write_file->read_proc = proc_read;
write_file->write_proc = proc_write;
//call back함수를 연결해 주어야 한다.


4. 몇 가지 유의 사항

proc_dir_entry 구조체를 생성할 때 성공적으로 만들어지지 못하면 NULL값이 return되므로 에러처리를 해준다.

proc_dir_entry 구조체를 성공적으로 생성한 후에는 구조체에 있는 owner의 값에 THIS_MODULE을 넣어주어야 제대로 동작한다.
즉, 위에 write_file을 예로 들면,
write_file->owner = THIS_MODULE 이런식으로 해주어야 한다.

init함수와 read/write call back함수에는 반드시 return값이 필요하다. 성공을 했을 경우에는 return 0를 해주자.
read/write함수에서는 데이타의 길이를 return하는 경우도 있다.


5. 모듈 컴파일

call back함수와 init/exit함수를 다 구현하였다면 컴파일을 해보자.
2.4커널 버전을 사용한다면 간단한 컴파일 명령으로 컴파일이 가능하다.

$ gcc -c -D__KERNEL__ -DMODULE -O test.c

그러나, 2.6버전의 커널을 사용한다면 Makefile이 반드시 필요하다.

$ vi Makefile

Makefile을 만들어보자.

obj-m := test.o //object file 이름이다.
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
  $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
  rm -rf *.ko
  rm -rf *.mod
  rm -rf *.o
  rm -rf *.cmd

.. 이것이 간단한 Makefile이다.

그리고 나서 컴파일을 하면 여러개의 파일이 마구 생긴다.
그 중에서 우리가 필요한 파일은 test.ko파일이다. (2.4커널버전에서는 test.o)
이것이 바로 module파일이다.


6. module 생성

$ lsmod

.. 라고 명령어를 실행시키면 지금 올라와 있는 module들이 보여진다.
이 곳에 만들어 놓은 module을 올려보자.

$ insmod test.ko

.. 이렇게 하면 만들어 놓은 test라는 이름으로 module이 올라가면서 /proc 아래에 프로그래밍 할 때 MODULE_NAME으로 주어진 이름으로 directory가 하나 생성된다.

$ cat /proc/MyProc/read

.. 라고 하면 read파일에 읽기로 접근한다.

$ echo "test" > /proc/MyProc/write

.. 라고 하면 write파일에 쓰기로 접근한다.

하는일은 call back함수에서 지정한 일을 하게 된다.

module을 삭제하고 싶으면 다음 명령어를 실행시킨다.

$ rmmod test

댓글 없음:

댓글 쓰기