Linux

procfs

aucd29 2013. 9. 26. 20:56
Linux Kernel Procfs Guide

저자: Eric (J.A.K.) Mouw (, Delft University of Technology, Faculty of Information Technology and Systems)

번역: 김남형 (, 서울시립대학교 인공지능연구실)



Contents

1 Preface
2 소개
3 Procfs 다루기
3.1 일반 파일 생성하기
3.2 심볼릭 링크 생성하기
3.3 장치 노드 생성하기
3.4 디렉토리 생성하기
3.5 엔트리 삭제하기
4 사용자 공간과 통신하기
4.1 데이타 읽기
4.2 데이타 쓰기
4.3 여러 파일에서 하나의 콜백함수 쓰기
5 몇가지 팁들
5.1 편리한 함수
5.2 모듈
5.3 파일 모드와 소유권
6 예제

1 Preface #

이 문서에서는 리눅스 커널에 포함된 proc 파일 시스템을 사용하는 방법에 대해 설명한다. 이 문서가 쓰여질 수 있도록 아이디어가 떠오른 것은 #kernelnewbies IRC 채널에서 (http://www.kernelnewbies.org 참고) Jeff Garzik 이 procfs 의 사용법을 설명하고 Alexander Viro 가 리눅스 커널 메일링 리스트에 올린 글을 나에게 보내주었을 때이다.

나는 이 동기를 마련해 주었던 Jeff Garzik 과 Alexander Viro 에게 감사하고, 또한 SelfDocbook (http://people.redhat.com/twaugh/docbook/selfdocbook) 을 통해 도움을 주었던 Tim Waugh 과 문서를 확인해 주었던 Marc Joosen 에게 감사의 말을 전한다.

이 문서는 Mobile Multi-media Communications (http://www.mmc.tudelft.nl) 과 Ubiquitous Communications (http://www.ubicom.tudelft.nl) 에서 지원받은 LART computing board (http://www.lart.tudelft.nl) 에서 테스트하고 작성되었다.

Erik


2 소개 #

/proc 파일 시스템 (procfs) 은 리눅스 커널 내에 존재하는 특별한 파일 시스템이다. 이것은 가상의 파일 시스템으로 실제 블록 디바이스에 연결되어 있는 것이 아니라 오직 메모리 상에만 존재한다. procfs 내의 파일은 사용자 공간의 프로그램이 커널의 특정 정보를 읽어오거나 (/proc/[0-9]+/ [1] 에서 해당 프로세스의 정보를 알아낼 수 있는 것처럼), 디버깅의 목적으로 (/proc/ksyms 와 같이) 사용될 수 있다.

이 문서에서는 리눅스 커널 내의 proc 파일 시스템의 사용 방법에 대해 설명한다. 먼저 proc 파일 시스템 내의 파일을 다루는데 관련된 모든 함수들을 소개하도록 한다. 그리고 사용자 공간의 프로그램과 어떻게 통신하는지를 설명하고, 몇가지 팁들을 소개한 후에 마지막으로는 이를 이용한 완전한 예제를 하나 보이도록 하겠다.

/proc/sys 내의 파일들은 시스템의 설정에 관련된 파일임에 주의하기 바란다: 이 파일들은 proc 파일 시스템에 속한 것이 아니고 Kernel API Book 상의 완전히 다른 API 들에 의해 관리된다.

3 Procfs 다루기 #

이 장에서는 procfs 에 파일, 심볼릭 링크, 장치 노드, 디렉토리 등의 여러 커널 컴포넌트 들을 생성하고 제거하는 함수들을 설명한다.

시작하기 전에 한가지 주의할 사항이 있다: 만약 procfs 의 함수들을 사용하기 위해서는 올바른 헤더파일을 포함시켜야 한다! 코드의 시작부분에 다음과 같은 부분이 들어야 할 것이다.

#include 

3.1 일반 파일 생성하기 #

struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode,
                                         struct proc_dir_entry* parent);

이 함수는 parent 라는 디렉토리 안에 파일 모드로 mode 를 가지는 name 이라는 이름의 일반 파일(regular file) 을 생성한다. procfs 의 가장 상위에 파일을 생성하기 위해서는 parent 자리에 NULL 을 넣으면 된다. 성공시 함수의 리턴값은 새로 생성된 struct proc_dir_entry 의 포인터가 되며, 실패시에는 NULL 이 된다. 3장 에서는 이러한 파일을 가지고 할 수 있는 일들을 설명한다.

한가지 특별한 점은 여러 개의 디렉토리를 넘어서 파일을 생성하는 것을 허용한다는 것이다. 예를 들어, create_proc_entry("drivers/via0/info") 와 같이 사용한 경우 via0 라는 디렉토리가 없다면 0755 의 권한으로 이 디렉토리를 생성하게 된다.

만약 오직 파일을 읽기만 할 목적이라면, 4.1 절에서 소개할 create_proc_read_entry 함수를 이용하여 생성과 초기화를 한번에 처리할 수 있다.

3.2 심볼릭 링크 생성하기 #

struct proc_dir_entry* proc_symlink(const char* name,
                        struct proc_dir_entry* parent, const char* dest);

이 함수는 parent 디렉토리 내에 name 으로부터 dest 를 가리키는 심볼릭 링크를 생성한다. 이것은 사용자 공간에서의 ln -s dest name 명령으로 생각할 수 있다.

3.3 장치 노드 생성하기 #

struct proc_dir_entry* proc_mknod(const char* name, mode_t mode,
                                 struct proc_dir_entry* parent, kdev_t rdev);

이 함수는 parent 디렉토리 내에 파일 모드로 mode 를 가지는 name 이라는 장치 노드 (device file) 를 생성한다. 장치 노드는 linux/kdev_t.h 내의 MKDEV 라는 매크로를 통해 만들어지는rdev 라는 장치와 함께 동작할 것이다. mode 에는 장치 노드를 생성하기 위해 반드시 S_IFBLKS_IFCHR 중의 하나를 포함해야 한다. 이것은 사용자 공간에서의 mknod --mode=mode name rdev 명령으로 생각할 수 있다.

3.4 디렉토리 생성하기 #

struct proc_dir_entry* proc_mkdir(const char* name,
                                  struct proc_dir_entry* parent);

이 함수는 parent 디렉토리 안에 name 이라는 이름의 디렉토리를 생성한다.

3.5 엔트리 삭제하기 #

void remove_proc_entry(const char* name, struct proc_dir_entry* parent);

이 함수는 procfs 내의 parent 디렉토리 안에 있는 name 이라는 이름의 엔트리 [2] 를 삭제한다. 각 엔트리는 name 을 통해 삭제되며, 각각의 생성 함수들을 통해 반환된 proc_dir_entry 구조체로는 지울 수 없다. 한가지 주의할 사항은 이 함수는 재귀적으로 엔트리를 삭제하지 않는다는 점이다.

만약 엔트리에 관련된 데이타가 할당되었다면, remove_proc_entry 함수를 호출하기 전에 데이타를 해제해야 한다. 3.3 절에서 이러한 데이타를 사용하는 법에 대해 더 자세히 알아보겠다.


4 사용자 공간과 통신하기 #

procfs 에서는 커널의 메모리를 직접 접근하여 정보를 얻어오는 대신에 파일의 콜백 함수 를 통해 동작한다: 이러한 함수들은 특정 파일을 읽거나 쓸 때 호출된다. 이를 이용하기 위해서는 procfs 의 파일을 생성한 후에 create_proc_entry 함수를 통해 반환된 proc_dir_entry 구조체의 read_proc 이나 write_proc 등의 필드를 초기화 해 주어야 한다:

struct proc_dir_entry* entry;

entry->read_proc  = read_proc_foo;
entry->write_prpc = write_proc_foo;

만약 read_proc 만을 사용하는 경우라면, 4.1 절에서 소개할 create_proc_read_entry 함수를 이용하면 생성과 초기화를 한번에 수행할 수 있다.

4.1 데이타 읽기 #

read 함수는 사용자 공간의 프로세스가 커널 영역의 데이타를 읽을 수 있도록 해주는 콜백 함수이다. read 함수는 다음과 같은 형태가 될 것이다:

int read_func(char* page, char **start, off_t off,
              int count, int* eof, void* data);

read 함수는 정보를 읽어와서 page 에 저장한다. 이 함수에서 offpage 내의 정보를 기록할 시작위치를, count 로 읽어올 바이트 수를 명시할 수 있지만, 대부분의 read 함수는 단순한 형태가 되고, 오직 적은 양의 정보 만을 반환하기 때문에 일반적으로 이 두 인자는 무시된다. (이것은 moreless 같은 페이지를 조절하는 프로그램을 무시하게 되지만, cat 은 여전히 동작한다.)

만약 offcount 인자가 적절히 사용되면, eof 인자는 파일의 끝 (End Of File) 에 도달했는지를 나타내는 신호로 사용될 것이다. (EOF 인 경우 eof 가 가리키는 곳의 값이 1로 설정될 것이다.)

start 인자는 커널내의 어느 곳에서도 사용되지 않는 것으로 보인다. data 인자는 여러 파일들에 대한 하나의 콜백 함수를 생성하는데 쓰일 수 있다. - 3.3 절 참고

read_func 는 반드시 page 에 기록된 바이트 수를 리턴해야 한다.

5장에서 read 콜백 함수가 어떻게 사용되는지 보게될 것이다.

4.2 데이타 쓰기 #

write 콜백 함수는 사용자 공간의 프로세스가 커널 공간에 데이타를 기록할 수 있도록 해준다. 따라서 이 함수는 일종의 커널에 대한 제어권을 가진다. write 함수는 다음과 같은 형태가 될 것이다:

int write_func(struct file* file, const char* buffer,
               unsigned long count, void* data);

write 함수는 buffer 로 부터 최대 count 만큼의 바이트를 읽어들인다. 주의할 사항은 buffer 는 커널 메모리 공간에 속한 것이 아니라는 것이다. 따라서 먼저 copy_from_user 를 통해 커널 영역으로 데이타를 복사해야 한다. file 인자는 보통 무시된다. 3.3 절에서 data 인자를 어떻게 사용하는지를 보여준다.

다시 말하지만, 5장에서 이 콜백함수가 어떻게 사용되는지 보게 될 것이다.

4.3 여러 파일에서 하나의 콜백함수 쓰기 #

거의 비슷한 파일이 아주 많이 사용될 때, 각각의 파일 마다 각각의 콜백 함수들을 사용해야 한다면 꽤 불편한 일이될 것이다. 이를 위해 하나의 콜백 함수가 struct proc_dir_entrydata 필드를 통해 각각의 파일들을 구분해서 사용할 수 있도록 하고 있다. 먼저 data 필드가 초기화 되어야 한다.

struct proc_dir_entry* entry;
struct my_file_data *file_data;

file_data = kmalloc(sizeof(struct my_file_data), GFP_KERNEL);
entry->data = file_data;

data 필드는 void * 형이므로, 어떠한 형태로든지 초기화 될 수 있다.

이제 data 필드를 설정해서, read_procwrite_proc 함수에서 파일들을 구분할 수 있게 할 수 있다.

int foo_read_func(char *page, char **start, off_t off,
                  int count, int *eof, void *data)
{
        int len;

        if(data == file_data) {
                /* special case for this file */
        } else {
                /* normal processing */
        }

        return len;
}

procfs 의 엔트리를 삭제할 때 data 필드를 해제하는 것을 잊지말자.


5 몇가지 팁들 #

5.1 편리한 함수 #

struct proc_dir_entry* create_proc_read_entry(const char* name, mode_t mode,
          struct proc_dir_entry* parent, read_proc_t* read_proc, void* data);

이 함수는 2.1 절에서 설명한 create_proc_entry 를 이용한 것과 완전히 동일한 방식으로 일반 파일을 생성하고 read_proc 을 통해 read 함수를 설정한다. 이 함수에서도 물론 3.3 절에서 설명한 data 인자를 설정할 수 있다.

5.2 모듈 #

만약 procfs 가 모듈 내에서 사용된다면, struct proc_dir_entry 내의 owner 필드를 THIS_MODULE 이라는 매크로를 이용해 설정해야 한다.

struct proc_dir_entry* entry;

entry->owner = THIS_MODULE;

5.3 파일 모드와 소유권 #

때때로 procfs 엔트리의 파일 모드나 소유권을 변환해야 할 때가 있다. 이러한 경우의 예를 아래에 보인다:

struct proc_dir_entry* entry;

entry->mode =  S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH;
entry->uid = 0;
entry->gid = 100;


6 예제 #

/*
 * procfs_example.c: an example proc interface
 *
 * Copyright (C) 2001, Erik Mouw (J.A.K.Mouw@its.tudelft.nl)
 *
 * This file accompanies the procfs-guide in the Linux kernel
 * source. Its main use is to demonstrate the concepts and
 * functions described in the guide.
 *
 * This software has been developed while working on the LART
 * computing board (http://www.lart.tudelft.nl/), which is
 * sponsored by the Mobile Multi-media Communications
 * (http://www.mmc.tudelft.nl/) and Ubiquitous Communications 
 * (http://www.ubicom.tudelft.nl/) projects.
 *
 * The author can be reached at:
 *
 *  Erik Mouw
 *  Information and Communication Theory Group
 *  Faculty of Information Technology and Systems
 *  Delft University of Technology
 *  P.O. Box 5031
 *  2600 GA Delft
 *  The Netherlands
 *
 *
 * This program is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include 
#include 
#include 
#include 
#include 
#include 


#define MODULE_VERSION "1.0"
#define MODULE_NAME "procfs_example"

#define FOOBAR_LEN 8

struct fb_data_t {
        char name[FOOBAR_LEN + 1];
        char value[FOOBAR_LEN + 1];
};


static struct proc_dir_entry *example_dir, *foo_file,
        *bar_file, *jiffies_file, *tty_device, *symlink;


struct fb_data_t foo_data, bar_data;


static int proc_read_jiffies(char *page, char **start,
                             off_t off, int count,
                             int *eof, void *data)
{
        int len;

        MOD_INC_USE_COUNT;

        len = sprintf(page, "jiffies = %ld\n",
                      jiffies);

        MOD_DEC_USE_COUNT;

        return len;
}


static int proc_read_foobar(char *page, char **start,
                            off_t off, int count,
                            int *eof, void *data)
{
        int len;
        struct fb_data_t *fb_data = (struct fb_data_t *)data;

        MOD_INC_USE_COUNT;

        len = sprintf(page, "%s = '%s'\n",
                      fb_data->name, fb_data->value);

        MOD_DEC_USE_COUNT;

        return len;
}


static int proc_write_foobar(struct file *file,
                             const char *buffer,
                             unsigned long count,
                             void *data)
{
        int len;
        struct fb_data_t *fb_data = (struct fb_data_t *)data;

        MOD_INC_USE_COUNT;

        if(count > FOOBAR_LEN)
                len = FOOBAR_LEN;
        else
                len = count;

        if(copy_from_user(fb_data->value, buffer, len)) {
                MOD_DEC_USE_COUNT;
                return -EFAULT;
        }

        fb_data->value[len] = '\0';

        MOD_DEC_USE_COUNT;

        return len;
}


static int __init init_procfs_example(void)
{
        int rv = 0;

        /* create directory */
        example_dir = proc_mkdir(MODULE_NAME, NULL);
        if(example_dir == NULL) {
                rv = -ENOMEM;
                goto out;
        }

        example_dir->owner = THIS_MODULE;

        /* create jiffies using convenience function */
        jiffies_file = create_proc_read_entry("jiffies",
                                              0444, example_dir,
                                              proc_read_jiffies,
                                              NULL);
        if(jiffies_file == NULL) {
                rv  = -ENOMEM;
                goto no_jiffies;
        }

        jiffies_file->owner = THIS_MODULE;

        /* create foo and bar files using same callback
         * functions 
         */
        foo_file = create_proc_entry("foo", 0644, example_dir);
        if(foo_file == NULL) {
                rv = -ENOMEM;
                goto no_foo;
        }

        strcpy(foo_data.name, "foo");
        strcpy(foo_data.value, "foo");
        foo_file->data = &foo_data;
        foo_file->read_proc = proc_read_foobar;
        foo_file->write_proc = proc_write_foobar;
        foo_file->owner = THIS_MODULE;

        bar_file = create_proc_entry("bar", 0644, example_dir);
        if(bar_file == NULL) {
                rv = -ENOMEM;
                goto no_bar;
        }

        strcpy(bar_data.name, "bar");
        strcpy(bar_data.value, "bar");
        bar_file->data = &bar_data;
        bar_file->read_proc = proc_read_foobar;
        bar_file->write_proc = proc_write_foobar;
        bar_file->owner = THIS_MODULE;

        /* create tty device */
        tty_device = proc_mknod("tty", S_IFCHR | 0666,
                                example_dir, MKDEV(5, 0));
        if(tty_device == NULL) {
                rv = -ENOMEM;
                goto no_tty;
        }

        tty_device->owner = THIS_MODULE;

        /* create symlink */
        symlink = proc_symlink("jiffies_too", example_dir,
                               "jiffies");
        if(symlink == NULL) {
                rv = -ENOMEM;
                goto no_symlink;
        }

        symlink->owner = THIS_MODULE;

        /* everything OK */
        printk(KERN_INFO "%s %s initialised\n",
               MODULE_NAME, MODULE_VERSION);
        return 0;

no_symlink:
        remove_proc_entry("tty", example_dir);
no_tty:
        remove_proc_entry("bar", example_dir);
no_bar:
        remove_proc_entry("foo", example_dir);
no_foo:
        remove_proc_entry("jiffies", example_dir);
no_jiffies:
        remove_proc_entry(MODULE_NAME, NULL);
out:
        return rv;
}


static void __exit cleanup_procfs_example(void)
{
        remove_proc_entry("jiffies_too", example_dir);
        remove_proc_entry("tty", example_dir);
        remove_proc_entry("bar", example_dir);
        remove_proc_entry("foo", example_dir);
        remove_proc_entry("jiffies", example_dir);
        remove_proc_entry(MODULE_NAME, NULL);

        printk(KERN_INFO "%s %s removed\n",
               MODULE_NAME, MODULE_VERSION);
}


module_init(init_procfs_example);
module_exit(cleanup_procfs_example);

MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");

EXPORT_NO_SYMBOLS;


----
   [1]  숫자는 pid 값을 의미
   [2]  위의 함수들로 만들어진 것들 - 일반파일, 심볼릭 링크, 장치노드, 디렉토리