디바이스 드라이버 작성
-> 디바이스 드라이버 작성 및 테스트 필요
- 처음부터 개발 <-> 개발 시간 절약을 위해 기존 다비이스 드라이버 참고 or 유사 기능 디바이스 드라이버 참고
- 커널 소스트리 driver 디렉토리 밑에있는 디바이스 드라이버 참고 or 공개된 디바이스드라이버 참고
-> 기능 검증으로 사용시 에러가 발생되지 않고, 사용자가 쉽게 써야함
(1) 디바이스 드라이버 개요
1) 디바이스 드라이버 제어
2) 디바이스 유형
3) 디바이스 드라이버
4) 주번호, 부번호
1) 디바이스 드라이버 제어
디바이스 제어 방법
1. /dev/mem 디바이스 파일 사용
- memory mapped io인경우 메모리 어드레스를 사용한 장치접근
- 디바이스 상태 진단을위해 주기적으로 상태점검해야하는 폴링방식 사용
-> 디바이스로부터 인터럽트 요구에 대한 핸들러 구현이 복잡
2. 디바이스 드라이버 사용
리눅스에서 디바이스 접근
- 사용자 프로그램 : 디바이스 파일을 사용해 접근
- 커널 : 디바이스 식별자(주번호, 부번호) 사용
- 주번호 : 디바이스 드라이버 식별
- 부번호: 같은 디바이스드라이버를 사용하는 여러개 드라이버 식별하기위해 사용
디바이스 파일
- 디바이스 드라이버에 대한 파일형태 인터페이스
- 디바이스 드라이버에 대한 접근과 일반 파일에 대한 접근이 동일
- 커널에서 사용하는 디바이스 파일은 /dev 디렉토리에 저장
디바이스 파일 생성
- 디바이스 파일 생성 : mknde [디바이스 파일이름] [디바이스 유형] [주번호] [부번호]
ex) mknod /dev/mydevice c 240 1
-> mydevice 라는 문자형 디바이스 파일을 /dev/디렉토리 밑에 만들고 주번호 240, 부번호 1로 설정
- 디바이스파일처리 : 저수준 파일입출력함수사용
저수준 파일입출력함수
- open 열기
- close 닫기
- read 읽기
- write 쓰기
- lseek 이동
- ioctl(제어)
- fsync(동기)
- 저수준 입출력함수는 버퍼를 사용하지 않고 직접 시스템 콜 호출
- 스트림 파일 입출력 함수 : 중간처리용 버퍼를 사용해 형식화된 입출력가능, 함수이름앞에 접두사 "f" 붙음
디바이스 드라이버 소스
- /usr/src/linux/drivers : 에 있는 소스들을 참고/변형하여 새로운 디바이스 드라이버 제작
- 모듈 형태로 디바이스 드라이버 개발시 -> 커널 재구성때 Loadable module support 옵션 선택해야함
2) 디바이스 유형
리눅스 디바이스 유형
1. 문자 디바이스
2. 블록 디바이스
3. 네트워크 디바이스
문자 디바이스
- 자료의 순차성을 지닌 디바이스
- 버퍼 캐시를 사용하지 않음
- 장치의 raw 데이터를 사용자에게 제공
ex) 터미널, 시리얼포트, 병렬포트, 키보드, 사운드카드, 스캐너
- 리눅스에서 문자 디바이스 : 디바이스 유형이 'c'로 표시
블록 디바이스
- 랜덤 액세스 가능
- 버퍼캐시를 사용한 블록단위 입출력
- 파일시스템에 의해 마운트 되어 관리되는 디바이스
ex) 디스크, RAM-DISK, CD-ROM
- 리눅스에서 블록 디바이스 : 디바이스 유형이 'b'로 표시
네트워크 디바이스
- 대응되는 디바이스 파일이 없음
- 네트워크를 통해 패킷을 송수신할수있는 디바이스
- 응용프로그램과의 통신은 socket(), bind() 등 전용 시스템 콜함수사용
ex) 이더넷, PPP, ATM
3) 디바이스 드라이버
개요
- 커널은 디바이스 드라이버를 통해 입출력 디바이스 액세스
- 디바이스를 제어하는데 사용되는 데이터 구조체와 함수들로 구성
- 사용자 : /dev 디렉토리에 저장된 특수 파일의 파일명을 사용해 액세스
- 커널 : 디바이스 드라이버와 디바이스 주번호, 부번호로 액세스
user program <-> device file <-> VFS <-> device driver <-> real device
* 사용자 프로그램과 디바이스 드라이버 인터페이스는 가상 파일 시스템 상에 구현된 디바이스 드라이버를 통해 됨
사용자 관점에서 디바이스 드라이버
- 사용자는 디바이스 자체 자세한 정보 알필요없음
->디바이스는 하나의 파일로 인식
- 디바이스 파일 접근을 통해 실제 디바이스에 접근 가능
4) 주번호, 부번호
주번호 major number
- 리눅스 커널에서 사용할 디바이스 드라이버의 식별자로 0~255 숫자할당
- 2.4 커널 8비트 할당, 2.6커널 12비트 할당
ex) 21: SCSI, 3 : IDE HDD
- 커널에서 사용하는 주번호 정보 : include/linux/major.h
부번호 minor number
- 동일한 디바이스 드라이버를 사용하는 서로 다른 장치 구분
- 2.4 커널 8비트
- 2.6 커널 20비트 할당
ex) IDE 하드디스크 드라이버의 주번호=3인경우 첫번째 하드디스크 부번호 0, 두번째 하드디스크 번호는 1
(2) 디바이스 드라이버 작성
1) 디바이스 드라이버 작성 절차
2) 디바이스 드라이버 등록과 삭제
3) 파일 처리 함수
4) 디바이스 드라이버 컴파일
1) 디바이스 드라이버 작성 절차
작성 절차
1. 디바이스 드라이버 모듈 프로그램 작성 및 컴파일
2. insmod 유틸리티를 통해 디바이스 드라이버 모듈 적재 (커널에서 디바이스 드라이버 주번호 할당)
3. mknod 를 이용해 할당된 주번호로 디바이스 파일 생성
4. lsmod 유틸리티로 디바이스 드라이버 정상 등록 유무 확인
5. 디바이스 사용
6. 사용끝나면 rmmod 유틸리티를 사용하여 디바이스 드라이버 모듈 해제
기본 함수
- init_module() : 디바이스 드라이버 등록, 메모리 할당, 초기화
- cleanup_module() : 디바이스 드라이버 제거, 할당된 IO memory 영역 변환
- 함수 : read(), ioctl(), write(), open(), release()
-> 사용자 공간과 커널 공간의 데이터 전송, 메모리에 값을 읽거나 씀
디바이스 드라이버 원형
- 파일 처리 함수 데이터 구조체 생성 및 구조체 맴버변수에 제작한 함수 매핑
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
// 헤더 파일
int device_open(){}
int device_release(){}
ssize_t device_write(){}
ssize_t device_read(){}
// 함수 프로토 타입
static struct file_operations device_fops =
{
read:device_read,
write:device_write,
open:device_open,
release:device_release
};
// 파일 처리 함수
int init_module(void){} // 모듈 설치시 초기화 수행
void cleanup_module(void){} // 모듈 제거시 반환 작업 수행
/*
디바이스 드라이버는 모듈 프로그램 사용
-> 모듈 프로그램 원형에 디바이스 드라이버에 필요한 파일 처리 함수부분 추가
*/
2) 디바이스 드라이버 등록과 삭제
디바이스 드라이버 등록
- 디바이스 드라이버를 커널에 등록하는 함수
- 문자형 디바이스 : int device_chrdev(unsigned int major, const char *name, struct file_operations *fops);
- 블록형 함수 : int register_blkdev(unsigned int major, const char *name, struct file_operations *fops);
- 파라미터
주번호 : 0을 주면 사용하지 않는 값반환
이름 : /proc/devices에 표시
fops : 디바이스와 연관된 파일 연선 구조체 포인터
- 음수가 반환시 오류가 발생한것. 디바이스 드라이버의 init_module()에서 호출
디바이스 드라이버 제거
커널에 등록되어있는 디바이스 드라이버 제거
- 문자형 디바이스 : int unregister_chrdev(unsigned int major, const char *name);
- 블록형 디바이스 : int unregister_blkdev(unsigend int major, const char *name);
- 디바이스 드라이버내 cleanup_module 루틴 안에서 호출
3) 파일 처리 함수
개요
- 디바이스 드라이버를 일반적인 파일과 유사한 인터페이스를 이용해 관리
- 디바이스는 파일 형태로 존재
- 커널은 파일 연산을 이용해 IO 연산수행하도록 인터페이스 구성
- 디바이스 드라이버 구현? -> 파일 연산 구조체에서 요구되는 기능들을 프로그래밍
ex) "mydev" character device의 파일 연산 구조체 예시
static struct file_operations mydev_fops = {
write:mydev_put,
read:mydev_get,
ioctl:mydev_ioctl,
open:mydev_open,
release:mydev_release,
};
void mydev_put(){};
void mydev_get(){};
void mydev_ioctl(){};
void mydev_open(){};
void mydev_release(){};
file_operations 구조체
- 디바이스에 따라 선택적으로 사용
- 디바이스 연고나 연산은 함수를 직접구현하여 이 구조체의 해당함수로 매핑
파일 연산 구조체의 맴버 연선
- lseek() : 파일 액세스 지점 이동
- read() : 디바이스로 부터 데이트 입력받음
- write() : 디바이스로부터 데이터를 출력받음
- readdir() : 디렉토리 엔트리읽음
- poll() : 디바이스로부터 이벤트를 대기하므로 현재 프로세스를 대기큐에 넣음
- ioctl() : 디바이스 파일을 제어하며, read/write 함수로 처리할수없는 입출력 데이터 처리에 사용.
- mmap() : 파일이나 디바이스를 현재 프로세스의 메모리공간에 매핑
- open(),release() : 디바이스 열기 닫기
- fsync() ; 버퍼에 남은 데이터를 모두 디바이스에 사용
4) 디바이스 드라이버 컴파일
- 일반 어플리케이션 컴파일 방법에도 별도 옵션 추가
-> 디바이스 드라이버는 모듈로 되어잇어 모듈프로그램 컴파일과 동일
ex) arm-linux-gcc -c -Wall -D__KERNEL__ -DMODULE -O2 driver.c
-> arm 용 크로스컴파일러를 사용한 예시
디바이스 드라이버 컴파일 옵션
- -c : 목적 파일이 insmod 명령을 통해 커널과 동적으로 링크 -> 링커를 호출하지 않고 컴파일만 진행
- -D__KERNEL__ : 소스의 선택적 컴파일
- -DMODULE : 소스의 선택적 컴파일을 위해 심볼 정의필요, 모듈 프로그램인 경우 커널과 모듈 심볼은 반드시 정의
- -O2 : 컴파일 최적화 레벨을 2로 설정. 컴파일러는 실행코드가 너무커지지 않는범위서
실행속도를 빠르게하는 최적화 작업 수행
디바이스 드라이버 컴파일
- makefile을 작성해 make
CFLAGS=-D__KERNEL__ -DMODULE -Wall -O2
MODULE_OBJS=hello.o
$(MODULE_OBJS):
arm-linux-gcc $(CFLAGS) -c hello.c
clean:
rm -f *.o
(3) 디바이스 드라이버 사용 및 분석
1) 디바이스 드라이버 사용 절차
2) 디바이스 드라이버 작성 예시
3) 디바이스 드라이버 분석
1) 디바이스 드라이버 사용 절차
디바이스 드라이버 사용절차
1. 디바이스 드라이버 로딩
- 컴파일한 모듈을 insmod 명령어로 로등
- mknod 명령어로 디바이스 파일 생성
2. 응용프로그램 작성
- open 함수로 디바이스 파일 열기
- 디바이스 드라이버에서 제공하는 함수를 이용해 디바이스 제어
- close()함수로 디바이스 파일 닫기
3. 디바이스 드라이버 제거
- rmmod 명령어로 모듈 제거
2) 디바이스 드라이버 작성 예시
모듈 프로그램 소스
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
int init_module(void){
printk("Start pf module!!\n");
}
void cleanup_module(void){
printk("end of module!!\n");
}
Makefile
CC=arm-linux-gcc
KEERNELDIR=/working/kernel/linux-2.4.20
INCLUDEDIR=-I$(KERNELDIR)/include -I./
CFLAGS=-D__KERNEL__ -DMODULE -Wall -O2 -I$(INCLUDEDIR)
MODULE_OBJS=mymod.o
MODULE_SRCS=mymod.c
$(MODULE_OBJS):
$(CC) $(CFLAGS) -c $(MODULE_SRCS)
clean:
rm -f *.o
드라이버 적재 및 삭제
# insmod mymod.o // 모듈을 커널에 로딩
Using mymod.o
Start of Module !! //init_module 함수가 실행되어 printk함수에 의해 출력
# lsmod
Module size Usedby
mymod 247 0(unused)
# rmmod mymod //cleanup_module 함수 실행
End of Module !!
3) 디바이스 드라이버 분석
디바이스 드라이버
- ARM 프로세서 기반 하드웨어 플랫폼에서 ARM의 GPIO에 연결된 LED를 제어하는 LED 디바이스 드라이버소스
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#define LED_MAJOR 0
#define LED_NAME "LED PORT"
#define LED_MODULE_VERSION "v1.0"
#define LED_ADDR 0xf1600000
#define LED_ADDR_RANGE 1
// LED 디바이스 주번호, 디바이스이름, 버전번호, 가상어드레스 주소, 어드레스 범위
static int led_usage = 0;
static int led_major = 0;
//LED 사용 여부 표시하는변수, 주번호 저장하는 변수
int led_open(struct inode *minode, struct file *mfile);
int led_release(struct inode *minode, struct file *mfile);
ssize_t led_writeb(struct inode *minode, const char *gdata, size_t length, loff_t *off_what);
static struct file_operations_led_fops = {
write:led_writeb,
open:led_open,
release:led_release,
};
int init_module(void){
int result;
result = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if (resulr <0){
printk(KERN_WARNING "can't get any major\n");
return result;
}
led_major =result;
//커널에 등록된 IO자원중 지정된 주소로부터 일정 크기 영역사용가능한지 여부 확인
if(!check_region(LED_ADDR, LED_ADDR_RANGE))
request_region(LED_ADDR, LED_ADDR_RANGE,LED_NAME); //시용영역확부
else
printk("Can't get IO region");
printf("init module, led major number : %d\n",result);
return 0;
}
void cleanup_module(void){
release_region(LED_ADDR, LED_ADDR_RANGE);
if (unregister_chrdev(led_major, LED_NAME))
printk(KERNEL_WARNING "%s driver cleanup failed \n",LED_NAME);
}
int led_open(struct inode *mindoe, struct file *mfile){
if (led_usage !=0) return -EBUSY;
MOD_INC_USE_COUNT;
led_usage=1;
return 0;
}
int led_open(struct inode *mindoe, struct file *mfile){
MOD_INC_USE_COUNT;
led_usage=0;
return 0;
}
//디바이스에 데이터를 써넣는 함수
ssize_t led_Writeb(struct file *inode, const char *gdata, size_t length, loff_t *off_what){
unsigned char *addr;
unsigned char c;
//사용자 영역변수인 gdata로부터 커널영역인 c변수로 데이터 전달받음
get_user(c, gdata);
addr=(unsigned char*)(LED_ADDR);
*addr=c;
return length;
}
응용 프로그램
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv){
int dev;
char buff;
if (argc <= 1) return -1;
dev = open("/dev/led", O_WRONLY);
if (dev != -1){
buff =atoi(argv[1]);
write(dev, &buff, 1);
close(dev);
}
else {
printf("Device open Error\n");
exit(-1);
}
return 0;
}
'컴퓨터과학 > 임베디드' 카테고리의 다른 글
펌웨어구현 환경구축 2 - 펌웨어 구현을 위한 소프트웨어 (0) | 2020.05.06 |
---|---|
펌웨어구현 환경구축 1 - 펌웨어란 (0) | 2020.05.06 |
ARM을 활용한 임베디드 시스템 설계 24 - 모듈 프로그래밍 (0) | 2020.05.05 |
ARM을 활용한 임베디드 시스템 설계 23 - 입출력 디바이스 제어 (0) | 2020.05.05 |
ARM을 활용한 임베디드 시스템 설계 22 - 플래시 파일 시스템 (0) | 2020.05.05 |