/proc/ioports - 등록된 I/O 영역을 확인할 수 있다.
Study/리눅스 디바이스 드라이버
- /proc/iomem, /proc/ioports 2011.09.20
- 문자 디바이스 드라이버 동작 2011.09.19
- vmalloc(), vfree() 함수 2011.09.16
- kmalloc(), kfree() 함수 2011.09.16
- 커널 내부의 모듈 관리 2011.09.16
- 모듈의 구현 원리 2011.09.16
- 커널 메시지 2011.09.16
- printk() 함수 2011.09.15
- 모듈 매개변수 (module_param) 2011.09.15
- MODULE_LICENSE 매크로 2011.09.15
/proc/iomem, /proc/ioports
/proc/ioports - 등록된 I/O 영역을 확인할 수 있다.
문자 디바이스 드라이버 동작
커널은 디바이스 파일에 기록된 디바이스 타입과 주 번호를 이용해 커널 내에 등록된 디바이스 드라이버 함수를 연결한다. 문자 디바이스 드라이버의 경우, 커널 2.6에서는 fs/char_dev.c에 chrdevs라는 전역 변수를 다음과 같이 정의 한다.
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
이 전역 변수는 struct file_operations *ops; 라는 필드(struct cdev 필드에 있음)를 포함한 문자 디바이스 드라이버를 관리하는 구조체다.
응용 프로그램에서 open() 함수로 디바이스 파일을 열어 타입 정보와 주 번호를 얻고, 이 정보를 이용하여 chrdevs 배열에 등록된 디바이스 드라이버의 인덱스를 얻고, 여기서 얻은 인덱스 값으로 chrdevs 변수에 등록된 file_operations 구조체 주소를 얻는다. 결국 디바이스 파일 타입 정보와 주 번호를 이용해 커널 내의 디바이스 드라이버를 찾는다.
vmalloc(), vfree() 함수
vmalloc() 함수에서 할당받은 주소와 kmalloc() 함수에서 할당받은 주소는 디바이스 드라이버에서 사용할 때 차이가 없다. 하지만 해당 주소의 실제(물리) 주소를 얻고자 한다면 vmalloc() 함수는 가상 주소 공간에서 할당받기 때문에 해당 주소의 영역이 하드디스크에 있을 수도 있어 실패할 수 있다.
vmalloc() 함수는 커다란 연속 공간을 할당하기 위해 가상 메모리 관리 루틴이 수행되기 때문에 kmalloc() 함수보다 할당 속도가 매우 느리다. 또한 vmalloc() 함수는 인터럽트 서비스 함수 안에서는 사용할 수 없다.
kmalloc(), kfree() 함수
단, kmalloc() 함수를 사용할 때는 할당 가능한 크기가 32 x PAGE_SIZE라는 점을 주의해야 한다. 그리고 메모리의 특성을 주거나 메모리 할당 시점에 처리 방식을 다음과 같은 매개 변수 값으로 줄 수 있다.
GFP_KERNEL
kmalloc() 함수에 사용하는 대표적인 인자값으로, 동적 메모리 할당이 항상 성공하도록 요구한다. 커널이 관리하는 메모리가 충분치 않을 경우에는 디바이스 드라이버를 호출한 프로세스가 수행을 멈추고, 동적 메모리를 할당할 수 있는 상태가 될 때까지 잠든다. 그러다가 다른 프로세스에서 메모리를 반환해 커널이 동적 메모리를 할당할 수 있는 상태가 되면 깨어 난다.
GFP_ATOMIC
커널에 할당 가능한 메모리가 있으면 무조건 할당하고, 없으면 즉시 NULL을 반환한다.
GFP_DMA
연속된 물리 메모리를 할당받을 때 사용한다. 디바이스 드라이버가 작동하는 메모리 공간은 물리적인 메모리가 아닌 가상 주소 메모리다. 프로세스 입장에서 보면 가상 주소 공간이 연속적으로 보여도 실제 물리적 공간은 분할되어 있을 수 있다.
커널 내부의 모듈 관리
리눅스 커널에서는 모듈을 다루기 위해 내부적으로 struct module 형식의 구조체를 선언하고, 리스트 형태로 모듈을 관리한다. 이 구주체는 include/linux/module.h에 선언되어 있다.
모듈 적재 과정
커널에 모듈을 적재하려면 insmod 명령을 실행해야 한다. 이 insmod는 다음과 같은 과정을 실행한다.
1. 주어진 모듈명이 ".o" 나 ".ko"가 포함되면 파일명에서 모듈명을 얻는다. 그렇지 않다면 모듈명으로 간주하고, /lib/module/의 하부 디렉토리에서 해당 모듈에 해당하는 파일명을 찾아 파일을 읽는다.
2. 읽어온 파일의 코드와 모듈명 그리고 module 구조체를 저장하는데 필요한 메모리 영역의 크기를 구한다.
3. 이 정보를 이용해 create_module() 함수를 호출한다. 이 함수는 모듈을 처리할 수 있는 권한이 있는지 검사하고, find_module() 함수를 이용해 이미 적재된 모듈인가를 검사한다. 만약 커널에 적재되지 않았다면 vmalloc() 함수를 호출해 새로운 모듈을 위한 메모리 영역을 할당한다. 할당받은 메모리 영역 중 module 구조체의 내용을 초기하고 모듈명을 그뒤에 복사한다. 이후 모듈을 관리하는 커널 모듈 리스트에 적재한다. 마지막으로 모듈에 할당된 메모리의 시작 주소를 돌려준다.
4. query_module() 함수를 이용해 커널 심볼 테이블과 커널에 적재된 다른 모듈의 심볼 테이블을 구한다. 이때 query_module() 함수에 QM_MODULES, QM_INFO, QM_SYMBOL 값을 지정하여 필요한 정보를 가져오는데, QM_MODULES는 커널에 포함된 모듈명을 얻어올 때 사용하며, QM_INFO는 각 모듈의 시작 주소와 크기를 얻기 위해 사용한다. 앞에서 구한 모듈 정보를 통해 QM_SYMBOL로 실질적인 커널 심볼 테이블과 커널에 적재된 다른 모듈의 심볼 테이블을 구한다.
5. 커널 심볼 테이블, 모듈 심볼 테이블 그리고 커널에 적재하려는 현재 모듈의 메모리 시작 번지를 이용해 읽어온 모듈의 프로그램 코드 주소를 재배치한다. 이때 모듈에서 참조하는 외부 함수나 변수의 외부 심볼과 전역 심볼에 대응하는 논리 주소 오프셋으로 바뀐다.
6. 사용자 모드 주소 공간에 메모리 영역을 할당하고, 이곳에 모듈 구조체의 내용과 모듈명 그리고 앞으로 재배치된 모듈 코드를 복사한다.
7. 모듈 구조체의 init 필드의 함수 주소와 exit 필드의 함수 주소를 할당한다.
8. 사용자 모드 주소 공간에 할당된 메모리 주소를 이용해 init_module() 함수를 호출한다. 이 함수는 create_module() 함수와 유사한 행동을 반복한다. 모듈 처리를 할 수 있는 권한이 있는지 검사하고, find_module() 함수와 create_module() 함수를 사용해 추가된 위치를 찾고, 사용자 모드에 설정된 모듈 구조체의 내용을 덮어쓴다. 이 후 module 구조체에 있는 주소들이 올바른지 검사한다. 이 후 모듈에 할당된 메모리에 나머지 내용을 모두 복사한다. 마지막으로 init 필드에 선언된 주소를 이용하여 모듈에 포함된 모듈 초기화 함수를 호출한다. 이후 사용자 모드 메모리를 해제하고 종료한다.
모듈이 커널에서 제거되는 과정
커널에서 모듈을 제거하려면 rmmod() 함수를 실행해야 하는데, 그 실행 과정은 다음과 같다.
1. 주어진 모듈명이 "*.o"나 "*.ko"가 포함되면 파일명에서 모듈명을 얻는다. 그렇지 않다면 모듈명으로 간주한다.
2. query_module() 함수를 이용해 모듈에 대한 정보와 사용되는 커널 심볼 테이블과 모듈 심볼 테이블을 얻는다.
3. 제거해야 할 모듈 목록을 이용하여 delete_module() 함수를 호출한다. 이 함수는 시스템을 이용하여 커널 심볼 테이블과 커널에 적재된 다른 모듈의 심볼 테이블을 구한다. 이때 모듈을 제거할 수 있는 권한이 있는가를 검사하고, find_module() 함수를 통해 이미 적재된 모듈인지 검사한다. 해당 모듈을 다른 모듈에서 참조하고 있는가를 검사한다. 참조하지 않고 있으면 혹시 사용되고 있는지를 검사한 후 사용중이 아니라면 module 구조체의 exit 필드에 정의된 함수를 호출한다. 이후 커널 내의 모듈 관리 리스트에서 해당 모듈을 제거하고, vfree() 함수를 이용하여 메모리를 해제한다.
4. 제거해야 할 모듈 목록을 모두 처리하면 종료한다.
모듈의 구현 원리
이 개념은 동적 라이브러리의 동작과 유사하다. 커널은 이미 링크가 끝난 상태기 때문에 심블릭 테이블이 제거된 상태이므로 그 자체로는 링크 처리를 할 수 없다. 그래서 커널은 내부적으로 심볼 테이블을 가지고 있다. 심볼 테이블은 커널 내부의 함수나 변수 중 외부에서 참조할 수 있는 함수의 심볼과 주소를 담은 테이블이다. 이 심볼 테이블을 이용하면 객체 형태로 작성된 커널 모듈 루틴이 참조할 커널 내부의 함수나 변수에 연결되어 동적으로 링크된다.
1. 커널에 외부 참조가 선언된 심볼 선언을 심볼 테이블에 등록한다.
2. 모듈은 커널에 적재될 때 커널 내의 심볼 테이블을 참고하여 참조 주소를 얻는다.
3. 모듈에 외부 참조된 심볼 선언은 객체와 테이블을 이용해 관리한다.
4. 모듈의 외부 참조가 선언된 심볼들을 커널 내의 심볼 테이블에 등록한다.
5. 모듈 코드에서 선언되지 않았던 주소를 모두 선언하면 커널에 등록된다.
커널에서 제공하는 심볼 테이블은 /proc/ksyms 또는 /proc/kallsyms 를 통해 알 수 있다. (리눅스 커널 버젼에 따라 ksyms나 kallsyms가 다르다)
커널 메시지
커널에서 메시지가 발생할 때마다 출력하고 싶다면
# cat /proc/kmsg
명령을 실행한다.~
printk() 함수
로그 레벨 지정
커널 메시지는 그 중요도에 따라 레벨을 정하고 이를 커널 메시지 선두에 표현하는 방법을 사용하는데, 이를 로그 레벨이라 한다.
상수 선언문 |
의미 |
#define
KERN_EMERG |
“<0>” /* 시스템이
동작하지 않는다. */ |
#define
KERN_ALERT |
“<1>” /* 항상 출력된다. */ |
#define KERN_CRIT |
“<2>” /* 치명적인
정보 */ |
#define KERN_ERR |
“<3>” /* 오류 정보 */ |
#define
KERN_WARNING |
“<4>” /* 경고 정보 */ |
#define
KERN_NOTICE |
“<5>” /* 정상적인
정보 */ |
#define KERN_INFO |
“<6>” /* 시스템
정보 */ |
#define KERN_DEBUG |
“<7>” /* 디버깅
정보 */ |
레벨에 대한 표시를 하지 않는다면 KERN_WARNING와 같은 레벨로 처리 된다.
ex) printk(KERN_INFO "system ok\n");
printk("<6>" "system ok\n");
printk("<6>system ok\n");
다음 세 문장의 의미는 같다.
원형 큐 구조 관리
커널 메시지는 콘솔 디바이스로 바로 출력되지 않고, 커널 내부에 있는 원형 큐 형식의 로그 버퍼라는 저장 공간에 저장된다. 이 로그 버퍼의 크기는 CONFIG_LOG_BUF_SHIFT 값으로 지정되는데, 2의 승수로 크기를 표현한다. 이 값은 include/config/log/buf/shift.h에 정의되어 있는데, 디폴트 값이 14이므로 로그 버퍼의 크기는 16 Kbyte 다.
커널 메시지를 저장하는 로그 버퍼는 출력하는 콘솔 디바이스와 데이터를 기록하는 로그 프로그램에 연결되어 있다. 그래서 printk() 함수에 의해 로그 버퍼에 저장되는 속도보다 데이터를 가져가는 속도가 느려서 로그 버퍼의 크기를 초과하면 가장 오래된 데이터가 손실된다.
모듈 매개변수 (module_param)
커널 2.6 이상에서 사용하는 매크로
module_param(변수명, 변수 타입, 접근 속성)
[변수 타입]
- short : short
- ushort : unsigned short
- int : int
- uint : unsigned int
- long : unsigned long
- charp : char *
- bool : int
- invbool : int
- intarray : int *
[접근 속성]
아주 특별한 경우가 아니면 보통 접근 속성은 0으로 지정하여 사용한다. 이는 사용자 권한에 따라 처리 허가 여부를 검사하는데, 일반 사용자가 모듈을 커널에 적재할 수 있는 경우는 매우 드물기 때문이다. 만약 굳이 접근 설정을 하고 싶다면 파일 퍼미션과 같은 개념으로 0644와 같은 8진수 값을 주면 된다.
MODULE_LICENSE 매크로
라이선스 |
풀어쓰기 |
GPL |
GNU Public
License v2 or later |
GPL v2 |
GNU Public
License v2 |
GPL and
additional rights |
GNU Public
License v2 rights and more |
Dual BSD/GPL |
GNU Public
License v2 or BSD license choice |
Dual MPL/GPL |
GNU Public
License v2 or Mozilla license choice |
Proprietary |
Non free products |