목 차 물리주소와 가상주소 mmap() 함수를 이용한 I/O 제어 커널 프로그래밍 리눅스 디바이스 드라이버 디바이스 드라이버 개요 및 구현 LED 디바이스 드라이버 GPIO 디바이스 드라이버 TextLCD 디바이스 드라이버
물리주소와 가상주소 물리주소 하드웨어적으로 설정된 고정 주소 물리주소 메모리 처리 -
물리주소와 가상주소 가상주소와 MMU MMU(Memory Management Unit) 프로세서에 전달되는 가상주소를 물리 주소로 변환한다. 변환 테이블을 참고하여 가상 주소를 물리 주소로 변환한다. 프로세서가 메모리에 접근하는 주소가 메모리에 직접 전달되는 것이 아니라 먼저 MMU에 전달되고, MMU는 변환 테이블을 참고해 이 주소를 물리주소로 변환해 전달한다. MMU가 필요한 이유 다중 프로세스를 지원하고, 각 프로세스에 대해 메모리 공간을 보호해야 하는 운영체제는 물리 주소만으로 구현하기 어렵고 안정된 동작을 보장할 수 없다. 메모리 보호 장치가 없기 때문에 다른 프로세스의 공간을 침범해도 막을 방법이 없다. 메모리의 할당과 해제가 빈번해지면 메모리 단편화가 발생 MMU의 동작 MMU 테이블을 유지하기 위한 별도의 메모리가 없다. MMU는 보통 프로세서에 내장되고, 시스템 메모리를 같이 사용한다. 그래서 프로세서가 처음 부팅하면 리눅스는 시스템 메모리의 일부분을 MMU 테이블에 할당하고, 관리할 정보를 MMU 테이블에 기록한다. 이 과정에서는 MMU가 동작하지 않는다. 리눅스 커널은 MMU 테이블에 관련된 정보를 메모리에 모두 기록한 후에 MMU 테이블에 해당하는 메모리 위치를 MMU에 알려주고 MMU를 동작시킨다. 프로세스가 생성되면 프로세스 동작에 필요한 메모리 관리 정보를 생성하고, MMU 테이블을 갱신한다. 리눅스에서 관리하는 가상 메모리 테이블 커널 모드에서 동작하는 메모리 공간을 관리하는 커널 MMU 메모리 테이블 운영체제에서 수행되는 각 프로세스의 사용자 모드 상태의 메모리 공간을 관리하는 MMU 메모리 테이블
물리주소와 가상주소 물리주소 공간을 커널 주소 공간으로 매핑 프로세스 메모리 매핑 ioremap()/iounmap() 함수 물리 주소를 가상 주소로 매핑시키고 가상 주소로 할당된 공간을 해제한다. void *ioremap(unsigned long offset, unsigned long size); void *iounmap(void *addr); 프로세스 메모리 매핑 응용 프로그램에서 직접 하드웨어 I/O 주소 공간을 메모리 복사 없이 직접적으로 사용 할 수 있도록 해준다. mmap() 함수는 응용 프로그램이 동작하는 프로세스의 메모리 영역에 디바이스 드라이버가 제공하는 물리 주소를 매핑하면 된다. 응용프로그램에서는 디바이스 파일로 하드웨어를 제어하기 위해 보통 read(),write(),ioctl() 함수를 사용한다. 이 함수들은 응용 프로그램에 하드웨어의 내부 구조를 숭겨주는 효과가 있지만 프로세스 메모리 공간과 커널 메모리 공간 사이의 메모리 전달 과정이 수반되기 때문에 비효율적이다. mmap() 함수 동작 원리 mmap() 함수는 원래 메모리 주소를 이용해 파일에 접근하도록 하는 함수이다. 그러나 디바이스 파일에 적용할 경우에는 디바이스에서 제공하는 물리 주소(I/O 메모리 주소 또는 할당된 메모리 공간 주소)를 응용 프로그램에서 사용할 수 있게 한다. 응용 프로그램이 디바이스 파일을 대상으로 mmap() 함수를 호출하면 커널은 메모리 매핑에 대한 사전 작업을 수행하고, 실제로 대입해야 하는 물리 주소를 얻을 수 있도록 디바이스 파일의 파일 오퍼레이션 구조체에 선언된 mmap() 함수를 호출한다. 디바이스 드라이버에 정의된 mmap() 함수는 매핑을 수행하고, 이렇게 처리된 주소는 응용 프로그램에서 호출한 mmap() 함수의 반환값이다.
mmap() 함수를 이용한 I/O 제어 메모리 장치 디바이스 응용 프로그램에서 특정 물리 메모리에 접근하는 방식 /dev/mem : 시스템 메모리 공간에 접근할 수 있는 장치 파일 응용 프로그램에서 특정 물리 메모리에 접근하는 방식 메모리 장치 디바이스를 통한 read(), write(), lseek() 함수를 이용 메모리 장치 디바이스를 통한 mmap 함수 이용 mmap() 함수 열린 파일 기술자를 통해 접근하는 파일의 내용과 관련된 메모리의 구역에 대한 포인터를 생성 파일이나 장치 디바이스를 메모리에 매핑시키는 함수
mmap() 함수를 이용한 I/O 제어 mmap() 함수의 원형 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); start : 매핑하기를 원하는 주소 length : 매핑 영역의 크기, PAGE_SIZE 단위 prot : 메모리 영역에 대한 접근 허용 권한 설정 PROT_READ : 페이지 읽기 허락 PROT_WRITE : 페이지 쓰기 허락 PROT_EXEC : 페이지 실행 PROT_NONE : 페이지 접근 불가 flags : 메모리 공간에 대한 공유 설정 MAP_PRIVATE : 다른 프로세스와 대응 영역을 공유하지 않는다. MAP_SHARED : 지정된 주소 이외의 다른 주소를 선택하지 않는다. MAP_FIXED : 객체에 대응시키는 다른 프로세스와 대응 영역을 공유한다. fd : 파일 기술자 offset : 파일 데이터의 시작을 변경 start : 현실적으로 해당 주소로 매핑되어 반환되는 경우가 매우 드물기 때문에 이 값은 희망하는 값이지 반드시 해당 주소로 매핑해야 하는 주소는 아니다. 0으로 지정하는 것은 커널에서 알아서 할당하고 반환해 달라는 의미 length : 커널에서 가상 주소를 다루는 단위가 PAGE_SIZE이며, 크기는 4KByte이다. fd 란 :
mmap() 함수를 이용한 I/O 제어 munmap() 함수 munmap() 함수 원형 int munmap(void *start, size_t length); start : mmap() 함수에서 반환한 값 length : mmap() 함수에서 지정한 크기
mmap() 함수를 이용한 I/O 제어 mmap() 함수 사용 방법 mmap() 함수 사용 예 open() 함수를 사용하여 파일 디스크립터를 얻는다. mmap() 함수를 사용하여 제어할 I/O의 포인터를 얻는다. 포인터에서 값을 읽거나 원하는 값을 쓴다. munmap() 함수를 사용하여 메모리 공간을 해제한다. close() 함수를 사용하여 파일 디스크립터를 반환한다. mmap() 함수 사용 예 int fd; char *addr; fd = open(“/dev/mem”, O_RDWR); addr = (char *)mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x08805000); munmap(addr, 4096); clese(fd); - /dev/mem 디바이스 파일 :
mmap() 함수를 이용한 I/O 제어 XM-Bulverde의 LED I/O 구조 LED 레지스터 구조 74LCX374 : LED_CS : LED에 대한 CS 신호 각각의 LED 라인은 D0~D7 까지의 데이터 라인에 연결되어 있다.
mmap() 함수를 이용한 I/O 제어 LED I/O 프로그램 실습(1) 프로그램 인수를 받아들여 십진수와 십육진수를 구별하여 값을 구한다. 메모리 디바이스(/dev/mem)를 오픈한다. mmap() 함수를 이용하여 타겟 보드의 LED 컨트롤 레지스터 메모리 어드레스를 매핑한다. 타겟 보드의 LED 컨트롤 레지스터의 값을 변환한다. munmap() 함수를 사용하여 매핑한 메모리를 해제한다. 메모리 디바이스를 닫는다.
mmap() 함수를 이용한 I/O 제어 LED I/O 프로그램 실습(2) 소스위치 : 배포CD/source/CYC2C8_Src/Mmap_App/bus-led/led.c 소스 작성 후 컴파일을 한다. 생성된 실행파일을 타겟에서 실행시킨다. [Host@localhost ~] arm-linux-gcc –o led led.c 부트로더에서 LED제어할 때 사용하던 물리주소에 매핑되는 가상 주소를 얻오와서 이 어드레스에 데이터를 쓴다.
커널 프로그램 커널 프로그램과 응용 프로그램과의 비교 실행 방식의 차이 응용 프로그램 : 순차적 프로그램 커널 프로그램 : 시스템 콜, 인터럽트에 의한 비동기적 프로그램 커널 프로그램 :
커널 프로그램 라이브러리 어드레스 공간 응용 프로그램 : 대부분의 라이브러리를 링크하여 사용가능 커널 프로그램 : 커널에서 export한 것만 사용 가능 ex) printf -> printk, malloc -> kmalloc 등 어드레스 공간 응용 프로그램과 커널 프로그램은 다음과 같이 서로 다른 어드레스 영역을 사용하여 각각의 프로그램 코드에 영향을 주지 않는다. 사용자 공간 커널 공간 1GByte 3GByte
커널 프로그램 사용자 공간과 커널 공간 Namespace pollution 응용 프로그램 : 사용자 공간에서 실행되며 직접적으로 하드웨어 장치나 메모리에 접근이 불가능 커널 프로그램 : 커널 공간에서 실행되며 직접적으로 하드웨어 장치나 메모리에 접근이 가능 Namespace pollution 응용 프로그램 : 현재 개발하는 프로그램에서만 함수와 변수 이름등을 구별하면 된다. 커널 프로그램 : 현재 개발중인 모듈 이외에도 커널 전반적인 함수와 변수의 이름에 충돌이 일어나지 않도록 주의해야 한다.
커널 프로그램 커널 프로그램 할 때 주의 사항(1) 라이브러리 Namespace pollution 어드레스 공간 응용 프로그램에서 사용하던 “stdio.h”와 같은 헤더 파일을 사용하지 않는다. 커널에서 포함하고 있는 헤더파일을 사용한다. ex)linux-2.6.12/include Namespace pollution 외부 파일과 링크를 하지 않을 모든 심볼을 static으로 선언 또는 외부 파일과 링크할 심볼은 symbol table에 등록한다. EXPORT_NO_SYMBOLS; EXPORT_SYMBOL(name); 전역 변수는 잘 정의된 prefix를 붙여 준다. ex)sys_open() 어드레스 공간 커널이 사용하는 stack의 크기는 제한되어 있고, 인터럽트 핸들러도 동일한 스택을 사용하므로 큰 배열을 사용하거나, recursion이 많이 일어나지 않도록 주의 해야한다. 응용 프로그램과 data를 주고 받기 위해(call by reference) 특별한 함수를 사용해야 한다. recursion :
커널 프로그램 커널 프로그램 할 때 주의 사항(2) Fault 핸들링 부동 소수형 수치 연산 커널은 하드웨어 접근에 어떠한 제한이 없기 때문에 커널에서의 에러는 시스템에 치명적인 결과를 발생시킨다. 함수 호출 등의 작업에는 에러 코드를 검사해주고 처리한다. 부동 소수형 수치 연산 커널 프로그램에서 부동 소스형 수치 연산은 처리하지 않는다.
커널 프로그램 커널 인터페이스 함수 커널 인터페이스 함수 분류 커널 프로그램은 일반적인 라이브러리를 사용하지 못하고 커널이 export해준 함수만을 사용할 수 있다. 커널 인터페이스 함수 분류 커널에서 제공하는 함수 중 커널 프로그램에서 자주 사용하는 함수는 아래와 같이 분류할 수 있다. I/O 처리 관련 함수 인터럽트 관련 함수 메모리 관련 함수 커널 메시지 관련 함수 디바이스 드라이버 관련 함수
커널 프로그램 I/O 처리 관련 함수(1) 디바이스 I/O 장치들과 데이터를 주고 받기 위한 함수 이 함수들을 사용하려면 #include <asm/io.h>를 소스에 포함시킨다. 각 함수의 구현 방식은 프로세서의 아키텍처마다 다르게 구현된다. I/O 맵드 입출력 처리 함수 unsigned char inb(unsigned short port); unsigned short inw(unsigned short port); unsigned long inl(unsigned short port); void outb(unsigned char data, unsigned short port); void outw(unsigned short data, unsigned short port); void outw(unsigned long data, unsigned short port); In으로 시작하는 함수은 I/O포트에서 데이터를 읽어올 때 사용 out으로 시작하는 함수들은 데이터를 써넣기 위해 사용 b,w,l은 각각 byte(8비트), word(16비트), long(32비트)의 의미로 처리되는 데이터의 크기 중앙처리 장치로 사용되는 프소세서는 하드웨어를 처리하기 위해 I/O 명령을, 메모리를 처리하는 명령과 다르게 다루는 경우와 I/O와 메모리를 동일하게 처리하는 경우로 나눌 수 있으며, I/O 명령이 별도로 존재하는 프로세서는 주로 인텔에서 생산되는 프로세서이다. I/O 맵드 I/O(I/O Mapped I/O) : I/O 주소와 메모리 주소를 다르게 지정하는 주소 지정 방식 메모리 맵드 I/O(Memory Mapped I/O) : I/O 주소와 메모리 주소의 구별 없이 주소를 지정하는 방식 처리되는 데이터의 크기(byte,word,long)는 처리하고자 하는 시스템의 I/O 버스폭과 관련이 있다. 함수들은 아키텍처별로 다르게 구현되기 때문에 디바이스 드라이버는 리눅스 커널을 사용하려는 아키텍처에서 어떻게 구현하고 있는지 헤더 파일을 참고하여 검토한다. I/O 포트란 : 외부장치와 CPU를 연결해 주는 병렬 입출력 입출력 인터페이스 역할 : 외부장치 제어시 외부로부터 입력을 받아들이거나 제어신호를 외부로 보냄 -> “마이크로컨트롤러의 손” I/O 포트는 컨트롤러의 내부 레지스터를 통해서 제어, 외부로부터 받아들일 때 :입력포트(Read)/외부로 내보낼 때 :출력포트(Write) I/O 포트를 사용하는 이유 외부 장치와 CPU는 동작 속도가 서로 다르다 전압 레벨이 다르다. 전송 사이클이 다르다.
커널 프로그램 I/O 처리 관련 함수(2) 메모리 맵드 입출력 함수 ARM 같은 프로세서나 PCI 디바이스는 하드웨어가 I/O 포트에 연결되어 있지 않고 메모리 공간에 연결 I/O는 반드시 메모리에 데이터가 적용되어야한다 unsigned char readb(unsigned int addr); unsigned short readw(unsigned int addr); unsigned long readl(unsigned int addr); void writeb(unsigned char data, unsigned short addr); void writew(unsigned short data, unsigned short addr); void writel(unsigned long data, unsigned short addr); in과 out은 앞서 다룬 함수들과 동일 in은 read, out은 write에 해당한다. - i386의 스트림 I/O 명령어들은 뒤에 s가 붙어 있는 것을 사용
커널 프로그램 인터럽트 관련 함수 인터럽트의 설정 및 처리에 관한 함수 local_irq_disable(void) : 프로세서의 인터럽트 처리를 금지한다. local_irq_enable(void) : 프로세서의 인터럽트 처리를 금지한다. local_save_flags(unsigned long frags) : 현재의 프로세스의 상태를 저장한다. local_irq_restore(void) : 저장된 프로세스의 상태를 복구한다. int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long frags, const *device, void * dev_id); IRQ에 대한 인터럽트 서비스 함수를 커널에 등록,한다. void free_irq(unsigned int irq, void *dev_id); request_irq() 함수로 등록되어 있던 인터럽트 서비스 함수를 제거한다.
커널 프로그램 메모리 관련 함수(1) 커널에서 동적 메모리를 할당할 때 사용하는 함수들 get_free_page(), free_page() 한 페이지의 메모리를 할당하고 해제하는데 사용 페이지가 물리적으로 연속적(가상적으로도)이 되는 것을 보장 kmalloc(), kfree() 물리적으로 연속된 메모리를 할당하고 해제하는데 사용 할당 가능한 크기가 32xPAGE_SIZE(4096Byte)로 제한 vmalloc(), vfree() 가상 공간에서 연속적인 메모리를 할당하고 해제하는데 사용 페이지가 가상 주소 공간 내에서만 연속이 되는 것을 보장 큰 메모리 공간을 할당할 때 주로 사용 가상 공간이 허용하는 한 크기 제한 없이 할당 가능 인터럽트 서비스 함수 안에서는 사용할 수 없다. kmalloc()함수 : malloc()함수와 다른 점은 할당된 메모리의 특성을 주거나 메모리 할당 시점에 처리 방식을 매개변수값으로 줄 수 있다. PAGE_SIZE의 크기는 4096이다. 32xPAGE_SIZE는 131072Byte이다. vmalloc() 함수 : 이 함수는 가상 주소 공간에서 할당받기 때문에 해당 주소영역이 하드디스크에 있을 수도 있어 실패할 수 있다.그리고 커다란 연속 공간을 할당하기 위해 가상 메모리 관리 루틴이 수행되기 때문에 kmalloc()함수보다 할당 속도가 느리다. 대부분의 커널 코드는 메모리 할당을 위해 kmalloc()을 사용한다. 그 근복적인 이유는 성능때문이다. vmalloc() 함수는 물리적으로 연속되지 않은 페이지를 가상주소공간에서 연속적이 되도록 하므로 페이지 테이블에 특별한 조작을 가해야한다. 게다가 vmalloc()으로 얻은 페이지는(물리적으로는 연속적이지 않기때문에) 각자의 페이지에 의해 매핑돼야 하는데, 직접 매핑된 메모리를 쓸 때보다 TLB를 훨씬 많이 참조하게 된다. TLB란 : 대부분의 아키텍처에서 가상주소를 물리주소로 매핑하는 과정을 캐시(cache)하기 위해 사용하는 하드웨어 버퍼이다. 대부분의 메모리 접근은 가상주소를 이용하므로 TLB는 시스템의 성능을 크게 향상시킨다.
커널 프로그램 메모리 관련 함수(2) 사용자 공간과 커널 공간 사이에 데이터를 공유하기 위한 함수 get_user(x, ptr) ptr이 가리키는 사용자 공간의 메모리에서 x 변수 크기 만큼 읽어온다. x : 커널 변수, ptr : 사용자 메모리 블록의 선두 주소 put_user(x, ptr) ptr이 가리키는 사용자 공간의 메모리에 x 변수 크기만큼 읽어들여 써넣는다. x : 커널 변수 , ptr : 사용자 메모리 블록의 선두 주소 copy_to_user(void *to, void *from, unsigned long size) From 주소에서 to의 주소(사용자 공간)까지 size 만큼 주소의 값을 복사한다. copy_from_user(void __user *to, const void *from, unsigned long n) from이 가리키는 주소의 사용자 메모리 블록 데이터를 to가 가리키는 커널 메모리 블록 데이터에 바이트 크기 단위인 n 만큼 써넣는다. copy_to_user( from이 가리키는 주소의 커널 메모리 블록 데이터를 to가 가리키는 사용자 메모리 블록 데이터에 바이트 크기 단위인 n 만큼 써넣는다.
커널 프로그램 커널 메시지 관련 함수 standard out 으로 메시지를 출력하기 위한 함수 printk(constchar *fmt, ….) printf의 커널 버전 printk(LOG_LEVEL_ message) LOG_LEVEL : KERN_EMERG, KERN_ALERT, KERN_ERR, KERN_WARNING, KERN_INFO, KERN_DEBUG 예제) printk(“<1> Hello, World”); printk(“kernel value=0x%X\n”, value); printk(KERN_WARNING”warning…\n”);
커널 프로그램 디바이스 드라이버 등록 함수 int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); 문자 디바이스 드라이버를 커널에 등록 int unregister_chrdev(unsigned int major, const char *name); 커널에 등록되어 있는 문자 디바이스 드라이버를 제거 int register_blkdev(unsigned int major, const char *name, struct block_device_operations *fops); 블록 디바이스 드라이버를 커널에 등록 int unregister_blkdev(unsigned int major, const char *name); 커널에 등록되어 있는 블록 디바이스 드라이버를 제거
리눅스 디바이스 드라이버 디바이스(Device) 디바이스 드라이버 네트워크 어댑터, LCD 디스플레이어, PCMCIA, Audio, 터미널, 키보드, 하드디스크, 플로피 디스크, 프린터 등과 같은 주변 장치들을 말함 디바이스의 구동에 필요한 프로그램, 즉 디바이스 드라이버가 필수적으로 요구됨 디바이스 드라이버 실제 장치를 추상화 시켜 사용자 프로그램이 정형화된 인터페이스를 통해 디바이스를 접근할 수 있도록 해주는 프로그램 디바이스 관리에 필요한 정형화된 인터페이스 구현에 요구된는 함수와 자료구조의 집합체 표준적으로 동일 서비스 제공을 목적으로 커널의 일부분으로 내장 응용프로그램이 H/W를 제어할 수 있도록 인터페이스를 제공 하드웨어 독립적인 프로그램 작성을 가능하게 함
리눅스 디바이스 드라이버 사용자 관점에서의 디바이스 드라이버 사용자는 디바이스 자체에 대한 자세한 정보를 알 필요없음 디바이스는 하나의 파일로 인식됨 디바이스 파일에 대한 접근을 통하여 Real Device에 접근 가능 User Program Device file VFS Device Driver Real Device . 응용 프로그램 디바이스 파일 디바이스 드라이버
리눅스 디바이스 드라이버 응용 프로그램에서 하드웨어를 제어하는 방법 디바이스 파일 하드웨어 처리가 필요한 응용 프로그램이 파일 입출력 함수를 이용해 하드웨어와 대응되는 디바이스 파일에 데이터를 읽거나 쓰면, 디바이스 파일과 연동된 디바이스 드라이버가 동작하여 하드웨어를 제어한다 디바이스 파일 응용 프로그램이 하드웨어를 제어할 수 있도록 해준다 “/dev” 디렉토리에 모아 관리한다 mknod 유틸리티에 의해서 생성 mknod [디바이스 파일명] [디바이스 파일형] [주번호] [부번호] [Host@localhost ~]$ mknod /dev/devfile c 240 1 디바이스 파일을 사용해 하드웨어를 다루지만 디바이스 파일 자체가 하드웨어는 아니고, 일종의 정보 파일이다. 일반 파일의 목적이 데이터를 저장하는 하는 것이라면 디바이스 파일의 목적은 하드웨어를 다루는 디바이스 드라이버의 정보를 커널에 제공하는데 있다. 하드웨어를 제어하기 위해 커널에서 제공하는 함수들은 응용 프로그램이 라이브러리 함수를 직접 호출하여 사용하는 것과는 달리 직접적인 호출이 금지되어 있다. 직접적인 호출을 금지하고 있는 이유는 리눅스 시스템이 멀티태스킹을 지원하는 시스템이기 때문이다. 즉 디바이스 하나를 응용 프로그램 하나에서만 사용하는 게 아니라 두 개 이상의 서로 다른 응용 프로그램에서 사용할 수 있기 때문에 어느 한 응용 프로그램이 디바이스를 직접적으로 제어하면 다른 응용 프로그램에게 영향을 끼칠 수 있다. 응용 프로그램이 커널에게 디바이스에 대한 제어를 요청하면 커널이 해당 디바이스 제어 함수를 호출하는 방식으로 구동된다. 디바이스 파일의 개념을 제공 직접적인 호출 금지의 한계를 극복하기 위해 리눅스 커널은 하드웨어를 제어하거나 커널 내부에서 특정 기능을 수행하는 함수를 사용하는 방법으로 “디바이스 파일을”이라는 개념을 제공한다. 디바이스 파일이 한 프로세스에 의해서 이미 오픈되어 사용되고 있는 상태에서 또 다른 프로세스가 이를 사용하려 할 경우에는 이미 파일이 오픈 되어 있다는 에러를 리턴한다.
리눅스 디바이스 드라이버 디바이스 파일과 일반 파일의 차이점 일반 파일 디바이스 파일 프로그램에서 데이터를 쓰면 보존되고, 쓴 만큼 크기가 증가한다. 디바이스 파일 실질적인 하드웨어를 표현하므로 데이터를 써도 보존되지 않는다. 하드웨어로 데이터를 전달하고 하드웨어에서 발생한 데이터를 읽어온다. 저수준 파일 입출력 함수 시스템에서 제공하는 파일 함수\ 시스템 콜로 제공하는 것을 glibc 라이브러리에서 C 함수 형태로 제공하는 함수 스트림 파일 입출력 함수 저수준 파일 입출력 함수를 바탕으로 중간 버퍼 개념을 도입해 스트림 처리가 가능하고, 프로그램에서 사용하기 쉽게 만든 함수 내부적으로 입출력을 효율적으로 처리하기 위해 중간 처리용 버퍼가 존재 단순히 데이터를 읽거나 파일에 쓰는 것뿐만 아니라 fprintf(), fscanf()와 같은 함수를 이용해 형식화된 형태로 읽거나 쓸 수 있다.
리눅스 디바이스 드라이버 저수준 파일 입출력 함수 리눅스 커널에서 제공하는 파일 관련 시스템 콜을 라이브러리 함수로 만든 것 수행 내용이 파일에 바로 적용되므로 디바이스 파일을 대상으로 사용됨 저수준 파일 입출력 함수 기 능 open() 파일이나 장치를 연다. close() 열린 파일을 닫는다. read() 파일에서 데이터를 읽어온다. write() 파일에 데이터를 쓴다. lseek() 파일의 쓰기나 읽기 위치를 변경한다. ioctl() read(), write()로 다루지 않는 특수한 제어를 한다. fsync() 파일에 쓴 데이터와 실제 하드웨어의 동기를 맞춘다. 저수준 파일 입출력 함수를 이용하면 디바이스 파일에 연관된 디바이스 드라이버 함수가 바로 동작한다.
리눅스 디바이스 드라이버 리눅스 디바이스 드라이버 구조 Application System Call Interface VFS Network Subsystem Buffer Cache Network D/D Block D/D Char Device Driver Device Interface Hardware Application area Kernel area
리눅스 디바이스 드라이버 리눅스에서의 디바이스 디바이스 드라이버의 종류 리눅스에서 디바이스는 특별한 하나의 파일처럼 취급되고, access가 가능함 사용자는 File operation을 적용할 수 있음 각 디바이스는 Major Number와 Minor Number를 가짐 디바이스 드라이버의 종류 문자 디바이스 드라이버 블록 디바이스 드라이버 네트워크 디바이스 드라이버
리눅스 디바이스 드라이버 문자 디바이스의 특징 리눅스에서의 문자 디바이스 자료의 순차성을 지닌 장치 버퍼 캐쉬를 사용하지 않음 장치의 raw data를 사용자에게 제공 Terminal, Serial/Parallel, Keyboard, Sound Card, Printer 등 리눅스에서의 문자 디바이스 crw-r--r-- 1 root root 11, 1 Oct 7 2003 touch1 crw-r--r-- 1 root root 204, 5 Oct 7 2003 ttyS0 crw-r--r-- 1 root root 204, 6 Oct 7 2003 ttyS1 crw-r--r-- 1 root root 180, 0 Oct 7 2003 usb ls -al /dev/ttyS* 을 통해서 호스트 PC와 타겟에서 문자 디바이스 파일을 확인한다.
리눅스 디바이스 드라이버 블록 디바이스의 특징 리눅스에서의 블록 디바이스 ramdom 엑세스 가능 블록 단위의 입출력이 가능한 장치 버퍼 캐쉬에 의한 내부 장치 표현 파일 시스템에 의해 마운트되어 관리되는 장치 리눅스에서의 블록 디바이스 brw-r----- 1 root root 1, 0 Oct 7 2003 ram0 brw-r--r-- 1 root root 241, 1 Oct 7 2003 mmca1 brw-r--r-- 1 root root 31, 0 Oct 7 2003 mtdblock0 brw-r--r-- 1 root root 31, 1 Oct 7 2003 mtdblock1 ls -al /dev/mtd* 을 통해서 호스트 PC와 타겟에서 블록 디바이스 파일을 확인한다.
리눅스 디바이스 드라이버 네트워크 디바이스의 특징 대응하는 장치파일이 없음 네트워크 통신을 통해 패킷을 송수신 할 수 있는 장치 응용 프로그램과의 통신은 표준 파일 시스템관련 콜 대신 socket(), bind() 등의 시스템 콜을 사용 Ethernet, PPP, ATM, ISDN 등이 있음
리눅스 디바이스 드라이버 주번호(Major Number) 부번호(Minor Number) 응용 프로그램과 디바이스 드라이버를 연결하는 고리 같은 디바이스의 종류를 지칭 주번호 12비트 부번호(Minor Number) 디바이스 드라이버 내에서 장치를 구분하기 위해 사용 각 디바이스의 부가적인 정보를 나타냄 하나의 디바이스 드라이버가 여러 개의 디바이스 제어 가능 부번호 20비트 crw------- 1 root root 4, 64 Jan 1 00:00 ttyS0 crw------- 1 root root 4, 65 Oct 7 2003 ttyS1 crw-r--r-- 1 root root 4, 74 Oct 7 2003 ttyS10 crw-r--r-- 1 root root 4, 75 Oct 7 2003 ttyS11 응용 프로그램이 디바이스 파일을 open() 함수로 열면 커널은 해당 디바이스 파일에서 주 번호를 얻어 이 번호가 처리하는 디바이스 드라이버를 찾는다. 응용 프로그램과 디바이스 드라이버가 연결되면 부 번호가 처리하고자 하는 실질적인 디바이스를 찾는다. 디바이스 파일과 미리 예약(지정)된 주 번호 및 부 번호에 대해서는 Documentation/devices.txt 파일에 정리되어 있다.
리눅스 디바이스 드라이버 모듈프로그래밍(1) 모듈 프로그램 컴파일 방법 Makefile을 작성하여 make를 통해서 소스를 컴파일한다. 일반 어플리케이션 컴파일 방법에 약간의 옵션들을 추가 Makefile 작성 예 CC = arm-linux-gcc obj-m := test.o KDIR := /working/linux-2.6.12 PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -f *.ko rm -f *.o rm -f *.mod.* makefile 이란 : $(MAKE) –C $(KDIR) SUBDIRS=$(PWD) modules -C 옵션 : 실행 디렉토리를 바꾼다. SUBDIRS : modules : 모듈 컴파일을 수행하라는 명령 make –help 를 통해서 옵션을 확인할 수 있다.
리눅스 디바이스 드라이버 모듈프로그래밍(2) 생성된 모듈(드라이버명.ko)을 로딩 커널에 적재된 모듈 목록 보기 모듈 제거 Insmod 드라이버명.ko ex) insmod driver.ko 커널에 적재된 모듈 목록 보기 lsmod 모듈 제거 rmmod 드라이버명 ex)rmmod driver (주의 .ko가 붙지 않음) 디바이스 파일 작성 mknod c /dev/디바이스명 주번호 부번호 ex)mknod c /dev/driver 230 0
리눅스 디바이스 드라이버 모듈 프로그래밍(3) 모듈 프로그램 작성(1) hello_init(), hello_exit() printk() 함수를 이용하여 콘솔에 어떤 함수가 실행되는지 알려준다. hello.c #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> int hello_init(void) { printk(“Hello, Kernel!\n”); } void hello_exit(void) { printk(“Good-bye, Kernel!\n”); module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("hanback.co.kr"); MODULE_DESCRIPTION("hello program"); MODULE_LICENSE("Dual BSD/GPL");
리눅스 디바이스 드라이버 모듈 프로그래밍(4) 모듈 프로그램 작성(2) Makefile CC = arm-linux-gcc obj-m := hello.o KDIR := /home/raoudi/bulverde/kernel/linux-2.6.12-xm_bulverde PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -f *.ko rm -f *.o rm -f *.mod.*
리눅스 디바이스 드라이버 모듈 프로그래밍(5) 모듈 컴파일 모듈 수행 컴파일을 통해서 생성된 hello.ko 파일을 nfs 디렉토리로 복사하거나 시리얼을 통해서 다운로드 하여 모듈을 커널에 삽입하고 제거해 본다. [Host@localhost ~] make [Host@localhost ~] insmod hello.ko Using hello.ko Hello, Kernel! [Host@localhost ~]lsmod Module size Used by hello 1088 0 - Live 0xbf045000 [Host@localhost ~]rmmod hello Good-bye, Kernel!
리눅스 디바이스 드라이버 문자 디바이스 드라이버의 동작 구조 디바이스 파일에 기록된 디바이스 타입과 주 번호를 이용해 커널 내에 등록된 디바이스 드라이버 함수를 연결한다. 응용 프로그램에서 open() 함수로 디바이스 파일을 열어 타입 정보와 주 번호를 얻는다. 이 정보를 이용하여 chrdevs 배열에 등록된 디바이스 드라이버의 인덱스를 얻는다. 인덱스값으로 chrdevs 변수에 등록된 file_operations 구조체 주소를 얻는다. file_operations 구조체에는 디바이스 드라이버가 문자 디바이스 드라이버를 등록하는 함수를 사용하여 저수준 파일 입출력에 대응하는 함수를 설정한 내용을 담고 있다. 문자 디바이스 드라이버 블록 디바이스 드라이버의 상대적인 개념으로 고안된 형태 시리얼이나 키보드 같이 바이트 단위로 처리되는 디바이스를 다룬다. 써넣은 데이터를 보존될 수도, 보존되지 않을 수도 있다. 처리 용량과 같은 개념이 없다. 파일의 끝과 같은 개념이 얺다.
리눅스 디바이스 드라이버 파일 연산 디바이스 드라이버를 일반적인 파일과 유사한 인터페이스를 이용하여 관리 각 디바이스는 파일 형태로 존재하고, 커널은 파일 연산을 이용하여 I/O 연산을 수행하도록 인터페이스를 구성 디바이스 드라이버를 구현한다는 것은 파일연산 구조체에서 요구하는 기능들을 프로그래밍한다는 것을 의미 파일 연산 구조체 정의의 예 Textlcd 문자 디바이스 드라이버 구현의 예 static struct file_operations textlcd_fops = { write : textlcdport_write, ioctl : textlcdport_ioctl, open : textlcdport_open, release : textlcdport_release, };
리눅스 디바이스 드라이버 파일 연산 구조체의 구조 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t,loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *,struct poll_table_struct *); int (*ioctl)(struct inode *,struct file *,unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); <중략> }
리눅스 디바이스 드라이버 문자 디바이스 드라이버의 기본 형태 #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> Header Files int device_open( … ) { … } int device_release( … ) { … } ssize_t device_write( … ) { … } ssize_t device_read( … ) { … } Function Prototypes static struct file_operations device_fops = { read : device_read, write : device_write, open : device_open, release : device_relese, }; File Operation int xxx_init(void) { … } 모듈 설치 시 초기화 수행 void xxx_exit(void) { … } 모듈 제거 시 반환 작업 수행
리눅스 디바이스 드라이버 문자 디바이스 드라이버 기본 함수 xxx_init() xxx_exit() 디바이스 드라이버 등록 메모리 할당 및 초기화 xxx_exit() 디바이스 드라이버 제거 할당된 I/O 메모리 영역 반환 open(), release() read(), write(), ioctl() … 메모리에 값을 쓰거나 읽음 디바이스 드라이버의 목적에 따른 일을 수행 사용자 공간과 커널 공간의 데이터 전송
리눅스 디바이스 드라이버 문자 디바이스 드라이버 등록 문자 디바이스 드라이버를 커널에 등록하고, 파일 연산을 정의하는 등의 초기화 작업을 수행 모듈 형태에서는 xxx_init()함수에서 초기화를 수행 문자 디바이스 드라이버 등록 함수 커널에 지정되어 있는 chrdevs 구조에 새로운 문자 디바이스를 등록 major number : 주번호, 0을 주면 사용하지 않는 값을 반환 name : 디바이스 이름으로 /proc/devices에 나타남 fops : 디바이스와 연관된 파일 연산 구조체 포인터 음수가 반환되면 오류가 발생했음을 나타냄 문자 디바이스 드라이버 제거 함수 int register_chrdev( unsigned int major, const * name, struct file_operations * fops); int unregister_chrdev( unsigned int major, const * name);
리눅스 디바이스 드라이버 문자 디바이스 드라이버 제거 문자 디바이스 드라이버 제거 함수 더 이상 사용되지 않는 문자 드라이버를 제거 주로 xxx_exit() 함수에서 호출됨 int unregister_chrdev( unsigned int major, const * name);
리눅스 디바이스 드라이버 LED 디바이스 드라이버 작성(1) XM-Bulverde의 CYC2C8 메모리 맵 참고:배포CD/Doc/Manual/Memory_GPIO_Description/XM_Bulverde_GPIO_Pin_Desciption.pdf
리눅스 디바이스 드라이버 LED 디바이스 드라이버 작성(2) LED 디바이스 드라이버 프로그램 구조 LED 응용 프로그램 소스위치 : 배포CD/source/CYC2C8_Src/Device_Driver/bus-led_driver led_init(), led_exit() 디바이스 드라이버 등록 및 해제 open(), release() I/O 메모리 할당 및 해제 write() get_user() 함수를 이용하여 사용자 공간의 데이터를 전송 전송 받은 데이터를 LED에 적용시킴 LED 응용 프로그램 프로그램 인수를 받고 그 값을 LED 디바이스 드라이버를 통해서 타겟 보드의 LED를 제어한다. open 함수를 이용하여 장치 파일을 연다 디바이스 드라이버에서 제공하는 함수를 이용하여 디바이스를 제어한다. close() 함수를 이용하여 장치 파일을 닫는다. iorenap()/ioremap_nocache() 함수 : 물리 주소를 가상 주소로 매핑시킨다.
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(1) GPIO란 GPIO 기능별 레지스터 General Purpose Input/Output 으로 입력이나 출력으로 사용되거나 인터럽트로 사용될 수 있는 입출력 포트 GPIO 기능별 레지스터 레지스터 기 능 GPLR GPIO 포트 핀의 상태를 보여준다 GPDR GPIO 포트 핀의 방향을 설정한다 GPSR GPIO 포트 핀이 output일 때 해당 비트를 셋 한다 GPCR output 핀이 셋 된 것을 clear 한다 GRER GPIO 포트 핀이 input일 때 rising-edge를 감지한다 GFER GPIO 포트 핀이 input 일 때 falling-edge를 감지한다 GEDR GRER이나 GFER로 설정된 값에 따른 결과를 설정한다 GAFR GPIO 핀을 alternate function으로 전환한다
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(2) GPIO 레지스터 맵 Register Type GPLR GPLR0 GPLR1 GPLR2 GPLR3 GPSR GPSR0 GPSR1 GPSR2 GPSR3 GPCR GPCR0 GPCR1 GPCR2 GPCR3 GPDR GPDR0 GPDR1 GPDR2 GPDR3 GRER GRER0 GRER1 GRER2 GRER3 GFER GFER0 GFER1 GFER2 GFER3 GEDR GEDR0 GEDR1 GEDR2 GEDR3 GAFR GAFR0_U GAFR0_L GAFR1_U GAFR1_L GAFR2_U GAFR2_L GAFR3_U GAFR3_L
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(3) GPIO Block Diagram Pin Direction Register : GPIO 핀의 입출력 방향을 설정한다. Pin Set and Clear Register : GPIO핀을 제어(“0”또는 “1”/ Low 또는 “High”)한다. Alternate Function Register : GPIO 핀의 기능을 설정한다. Edge Detect Status Register : Alternate Function이 “0”으로 설정 되었을 경우 GPIO 핀의 Edge 상태를 감지한다. Rising Edge Detect Enable Register : Rising Detect를 감지하도록 설정한다. Falling Edge Detect Enable Register : Falling Detect를 감지하도록 설정한다. Pin-Level Register : GPIO 핀의 Level 상태(“0”또는”1”)를 나타낸다.
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(4) GPIO 사용 방법 GPDR로 GPIO 핀의 방향을 설정한다. ex) GPDR0 = GPDR0 | 0x20000; GPIO 핀의 방향을 출력으로 설정했을 때 GPSR을 사용하여 해당 핀을 셋하거나 GPCR로 셋된 비트를 clear한다. ex) GPSR0 = GPSR0 | 0x20000; GPLR로 해당 비트 값의 상태를 알 수 있다. ex) val = GPLR0 & 0x20000;
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(5) GPIO 핀을 인터럽트로 사용하는 방법 GPDR로 GPIO 핀의 방향을 설정한다. ex) GPDR0 = GPDR0 & ~(0x20000); set_irq_type()를 이용하여 GPIO 핀 번호와 감지할 상태를 설정한다. ex)set_irq_type(IRQ_BUTTON, IRQT_FALLING); request_irq()를 이용하여 irq 핸들러를 등록한다. ex) res = request_irq(IRQ_BUTTON, &button_interrupt, SA_INTERRUPT, “Button”, NULL); enable_irq()를 이용하여 인터럽트를 활성화 시킨다. 모든 사용이 끝내고 disable_irq()와 free_irq()를 이용하여 irq를 제거한다.
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(6) 블러킹 I/O 대기 큐 대기 상태로 만드는 함수 void interruptible_sleep_on(struct wait_queue **q); void sleep_on(struct wait_queue **q); 대기 상태의 프로세스를 깨우는 함수 wake_up_interruptible(struct wait_queue **q); wake_up(struct wait_queue **q); 대기 큐 wait_queue_head_t 자료구조를 이용하여 정의 다음의 매크로를 사용하여 정의한다. DECLEARE_WAIT_QUEUE_HEAD(wq)
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(7) XM-Bulverde 에서는 GPIO 버튼으로 0번을 사용할 수 있도록 설계되어 있다. XM-Bulverde의 GPIO 맵 참고:배포CD/Doc/Manual/Memory_GPIO_Description/XM_Bulverde_GPIO_Pin_Desciption.pdf
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(10) GPIO 버튼 디바이스 드라이버 프로그램 구조 소스 위치 : 배포 CD/source/CYC2C8_Src/Device_Driver/gpio-button_driver gpio_init(), gpio_exit() 디바이스 드라이버 등록 및 해제 open(), release() GPIO 인터럽트 등록 및 해제 read() interrupt_sleep_on() 함수를 이용하여 인터럽트가 발생할 때 까지기다린 후 데이터를 복사하여 응용 프로그램에 전달 button_interrupt() 인터럽트가 발생하면 이 함수가 실행 wake_up_interruptible() 함수를 사용하여 자고 있는 interrupt_sleep_on() 함수를 깨운다.
리눅스 디바이스 드라이버 GPIO 버튼을 이용한 디바이스 드라이버 작성(11) GPIO 버튼 디바이스 드라이버 프로그램 실행 insmod 를 사용하여 드라이버 모듈을 커널에 로딩한다. mknod를 사용하여 드라이버에 해당하는 디바이스 파일을 작성한다. 드라이버를 이용한 [Host@localhost ~] insmod gpio.ko Using gpio.ko init module, gpio major number : 252 [Host@localhost ~] lsmod Module size Used by gpio 1088 0 - Live 0xbf045000 [Host@localhost ~] mknod /dev/gpio c 252 0 [Host@localhost ~] ./test enable_irq(8) unbalanced from bf045088 Please push the GPIO_0 port! GPIO_0 port was pushed! [Host@localhost ~] rmmod hello driver:GPIO DRIVER EXIT
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(1) TextLCD 모듈은 8bit 마이크로 프로세서가 내장되어 있고 2개의 레지스터가 존재한다. TextLCD 레지스터 TextLCD를 사용하기 위해서는 Instruction Register(IR)에 명령을 쓰고, Data Register(DR)에 표시하고자 하는 데이터 값을 쓰면 된다. TextLCD 모듈에서 데이터 라인이 8비트이고 제어 비트가 3비트이므로 디바이스 드라이버 에서는 하위 8비트는 데이터 라인으로 묶고 상위 3비트는 제어라인으로 사용한다. Instruction Register(IR) Text lcd 모듈의 환경설정. Data Register(DR) Text lcd 모듈에 글자를 표시하기 위한 데이터 값이 들어가는 레지스터.
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(2) TextLCD의 내부 구조도 및 회로도
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(3) TextLCD의 Write 타이밍도
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(4) TextLCD 모듈의 제어 명령표 기 능 제어 신호 제어 명령 Executed Time RS R/W D7 D6 D5 D4 D3 D2 D1 D0 Clear Display 1 1.64mS Return Home * Entry Mode Set I/D S 40uS Display on/off Control D C B Cursor or Display Shift S/C R/L Function Set D/L N F Set CG RAM Address CG RAM Address Set DD RAM Address DD RAM Address Read Busy Flag and Address BF Address Counter Data Write to CG RAM or DD RAM Write Address Data Read to CG RAM or DD RAM Read Address
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(5) TextLCD 모듈 초기화 Function Set(이진수 : 0011 xx00)을 써넣는다. Display ON/OFF Control(0000 1xxx)을 써넣는다. Entry Mode Set(0000 01xx)을 써넣는다. DD RAM 주소를 써넣는다. 문자 데이터를 연속적으로 써넣는다. 제어 비트 RS, R/W, E는 TextLCD 타이밍도에 맞춰서 제어한다.
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(6) XM-Bulverde의 CYC2C8 메모리 맵 참고:배포CD/Doc/Manual/Memory_GPIO_Description/XM_Bulverde_GPIO_Pin_Desciption.pdf
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(6) TextLCD 디바이스 드라이버 프로그램 구조 소스 위치 : 배포CD/source textlcd_init(), textlcd_exit() 디바이스 드라이버 등록 및 해제 open(), release() I/O 메모리 할당 및 해제 write() copy_from_user()를 이용하여 사용자 영역의 데이터를 전송 전송 받은 데이터를 TextLCD에 쓴다. ioctl() 명령에 따른 TextLCD 제어 TextLCD 응용 프로그램 구조 인수로 받은 문자열을 TextLCD 디바이스 드라이버를 통해서 타겟 보드의 TextLCD를 제어하는 프로그램을 작성
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(7) ioctl() 함수 함수 호출 명령 번호 읽기/쓰기 이외의 부가적인 연산을 위한 인터페이스 디바이스 설정 및 하드웨어 제어 함수 호출 int ioctl(int fd, int cmd, …); fd : 파일 기술자 cmd : 명령 번호 ... : 명령에 따른 인수 명령 번호 시스템에서 유일한 번호 정의 매크로 _IO(base,command) : 데이터 이동이 없다. _IOR(base,command) : 커널에서 응용 프로그램으로 데이터 이동 _IOW(base,command) : 응용 프로그램에서 커널로 데이터 이동 _IOWR(base,command) : 양방향 데이터 이동
리눅스 디바이스 드라이버 TextLCD 디바이스 드라이버(8) ioctl 사용 방법 함수 선언 함수 구현 int (*ioctl)(struct inode *inode, struct file *file, unsigned int cmd, unsigned long gdata); cmd : 명령 번호 gdata : 함수 호출시 넘겨 받는 인수 함수 구현 switch(cmd) { case: TEXTLCD_COMMAMD_SET: … break; case: TEXTLCD_FUNCTION_SET: … … }
시스템 호출 시스템 호출이란? 사용자 모드에서 사용할 수 있도록 밖으로 노출 되어 있는 커널 함수 소프트웨어 인터럽트를 통한 커널에 대한 서비스 요청 커널은 시스템 콜 인터페이스를 제공하며, 사용자 프로세스는 직접적으로 혹은 라이브러리를 이용해서 커널에 접근하게 되며, 시스템 콜을 하게되는 순간부터 사용자 모드에서 커널 모드로 전환되어 순행된다. 시스템 콜을 시작하면 커널의 코드가 사용자 프로세스의 환경(context) 아래에서 동작하게 되는 것이다. 실제로 시스템 콜은 운영체제가 제공하는 라이브러리라고 생각하면 된다. 시스템 콜을 일으키는 루틴을 wrapper 함수라 한다. 예를들면, (시스템 콜)open()은 시스템 콜 sys_open()을 호출하는 wrapper 함수이다.
시스템 호출 시스템 호출 구조
시스템 호출 시스템 콜의 종류 시스템 콜 핸들러 linux/include/asm-arm/unistd.h linux/arch/arm/kernel/calls.S #define __NR_exit (__NR_SYSCALL_BASE+ 1) #define __NR_fork (__NR_SYSCALL_BASE+ 2) #define __NR_read (__NR_SYSCALL_BASE+ 3) #define __NR_write (__NR_SYSCALL_BASE+ 4) #define __NR_open (__NR_SYSCALL_BASE+ 5) #define __NR_close (__NR_SYSCALL_BASE+ 6) … __syscall_start: /* 0 */ .long SYMBOL_NAME(sys_ni_syscall) .long SYMBOL_NAME(sys_exit) .long SYMBOL_NAME(sys_fork_wrapper) .long SYMBOL_NAME(sys_read) .long SYMBOL_NAME(sys_write) /* 5 */ .long SYMBOL_NAME(sys_open) …
시스템 호출 시스템 호출의 처리 과정 사용자 프로세스에서 시스템 콜인 fork() 함수를 호출하게 되면 라이브러리에 있는 for() 함수가 호출된다. 이때 fork() 함수는 swi 를 발생시켜 커널에 등록된 핸들러인 vector_swi를 호출하게 된다. vector_swi 에서는 현재 실행중이던 프로세스에 대한 정보(PC)를 저장하고 fork()에 대한 시스템 호출 번호를 찾아서 sys_call_table에 등록된 fork() 함수를 찾아서 이를 실행시킨다. 시스템 콜이 수행된 후에는 다시 원래 수행하던 프로세스를 수행한다. libc.a : 라이브러리 vector_swi : 소프트웨어 인터럽트 핸들러 schedule() do_signal() IDT(Interrupt Dexcriptor Table) 시스템 콜을 호출하면 최종적으로 트랩을 방생시키는데 이 것은 소프트웨어 인터럽트라고 이해하면 된다. I386에선 IDT를 통해 모든 인터럽트가 관리되는데 시스템 콜은 0x80 번의 인터럽트를 사용한다.
시스템 호출 리눅스 시스템에서 시스템 호출 함수 추가 과정 사용자 모드에서 프로그램 구현 과정 커널 모드에서 시스템 호출 구현 과정 시스템 호출 번호를 할당한다. 시스템 호출 테이블 함수를 등록한다. 시스템 호출 처리 함수를 구현한다. 사용자 모드에서 프로그램 구현 과정 구현한 시스템 호출 함수를 이용하여 프로그램을 작성한다.
시스템 호출 시스템 호출 처리 함수 구현(1) 커널 모드에서 시스템 호출 구현 시스템 호출 번호를 할당한다. 시스템 호출 테이블 함수를 등록한다. /* linux/include/asm-arm/unistd.h */ … 346 #define __NR_vserver (__NR_SYSCALL_BASE+313) 347 #define __NR_writeled (__NR_SYSCALL_BASE+312) /*추가된 부분 */ /* linux/arch/arm/kernel/calls.S */ … 327 /* 310 */ .long sys_request_key 328 .long sys_keyctl 329 // .long sys_semtimedopi /* 주석처리한다 */ 330 /* 312 */ .long sys_writeled /* 추가된 부분*/ __syscall_end: #endif
시스템 호출 시스템 호출 처리 함수 구현(2) linux/arch/arm/kernel/writeled.c /* system call function for XM-BULVERDE LED */ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/ioport.h> #include <linux/linkage.h> #define ADDRESSOFLED 0xf1700000 asmlinkage int sys_writeled(char x) { int status = 1; char *pled; if(!check_region(ADDRESSOFLED,1)){ pled = (char *)ADDRESSOFLED; *pled = x; } else status = -1; return status; }
시스템 호출 시스템 호출 처리 함수 구현(3) linux/arch/arm/kernel/Makefile # Makefile for the linux kernel. … O_TARGET := kernel.o # Object file lists. # HanBack added the writed.o Obj-y := arch.o compat.o dma.o $(ENTRY_OBJ) entry‐common.o irq.o process.o ptrace.o semaphore.o setup.o signal.o sys_arm.o time.o traps.o writeled.o $(O_OBJS_$(MACHINE)) Obj-m := include $(TOPDIR)/Rules.make # Spell out some dependencies that `make dep' doesn't spot entry-armv.o: entry-header.S $(TOPDIR)/include/asm-arm/constants.h
시스템 호출 시스템 호출 처리 함수 구현(4) 사용자 모드에서 프로그램 구현 과정 #include <stdio.h> 구현한 시스템 호출을 이용하여 프로그램을 작성한다. #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <asm-arm/unistd.h> /* Generate a system call for int writeled(char x) */ _syscall1(int,writeled,char,x); int main(int argc, char **argv){ int status; char val; if(argc <= 1) { printf("please input the parameter! ex)./call_writeled 0xa1\n"); return -1; } if(argv[1][0] == '0' && (argv[1][1] == 'x' || argv[1][1] == 'X')) val = (char)strtol(&argv[1][2],NULL,16); else val = (char)atoi(argv[1]); status = writeled(val); return status; }
시스템 호출 시스템 호출 처리 함수 구현(5) CC = arm-linux-gcc Makefile 작성 CC = arm-linux-gcc IDIR=/working/linux-2.6.12-xm_bulverde/include TEST_SRCS = writeled.c writeled: $(CC) $(TEST_SRCS) -I$(IDIR) -o $@ clean: rm -f *.o rm -f writeled