디바이스 드라이버 (Device Driver) - 3. file_operations 구조체




3.1 file_operations란?


  • 문자 디바이스 드라이버와 응용 프로그램을 연결하는 고리.
  • linux/fs.h에서 정의하는 이 구조체는 함수 포인터 집합이다.
  • 특정 동작 함수를 구현하여 가리켜야한다.
  • 지정하지 않으면 NULL로 남겨두어야 한다.



3.2 file_operations 구조체 및 필드 설명



  • file_operations 구조체

struct file_operations{

       struct moduel *owner;

       ssize_t (*read) (struct file *, loff_t, int);

       ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

       int (*open) (struct inode *, struct file *);

       int (*release) (struct inode *, struct file *);

       int (*mmap) (struct inode *, struct file *);


       ...


 



  • file_operations 구조체 필드 설명

- struct module *owner;
파일 오퍼레이션의 소유자를 나타낸다. 보통 <linux/module.h>에 정의되어 잇는 THIS_MODULE 매크로를 사용해 초기화 한다.

- loff (*llseek (struct file *, loff_t, int)
디바이스 드라이버의 파이 ㄹ포인터 위치를 강제로 이동시키는 함수를 지정한다.

- ssize_t (*read) (struct file *, char *, size_t, loff_t *);
디바이스에서 자료를 읽는데 사용한다. NULL 이면 -EINVAL 반환

- ssize_t (*write) (struct file *, const char *, size_t, loff_t * );
자료를 디바이스로 보낸다. NULL이면 -EINVAL 반환

- unsigned int (*poll) (struct file *, struct poll_table_struct * );
다중 입출력 처리를 가능하게 해주는 poll, epoll, select의 백엔드이다.

- int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
디바이스 관련 명령들을 제어할 수 있다.

- int (*unlocked_ioctl) (struct file *flip, unsigned int cmd, unsigned long arg);
모든 CPU에서 lock을 걸던 것을 개별적인 lock을 걸 수 있도록 ioctl에서 바꾼 함수.
함수에 진입 시 커널에 대한 락이 처리되어지지 않기 때문에 함수 내에서 별도의 동기화 기법을 사용해야 한다. 이 함수를 부르면 된다.



3.3 file_operations에 연결되는 함수



  • open 함수
디바이스 드라이버가 처음 열렸을 때 하드웨어 초기화
디바이스 드라이버의 동작에 필요한 에러 체크
- -ENODEV 하드웨어가 존재하지 않는다.
- -ENOMEM 커널 메모리가 부족하다.
- -EBUSY 디바이스가 이미 사용 중이다.
minor 번호에 대한 처리가 필요한 경우 file_operations 구조체를 갱신
- 보통 file 구조체 flip의 private_data에 등록하여 사용한다.

  • release 함수
device_close로 부르는 경우도 있다.
open이 flip->private data에 할당한 데이터의 할당 삭제
마지막 close 호출 시 디바이스 종료


  • write 함수

- 주요 매소드

 ssize_t xxx_write (struct file *flip, char *buff, size_t count, loff_t *offp)

  사용자 영역인 buff에서 count 바이트 만큼 읽은 후  디바이스의 offp 위치로 저장


*인자 설명
struct file *flip   읽기와 쓰기에 전달되는 flip은 디바이스 파일이 어떤 형식으로 열렸는가에 대한 정보를 저장
loff_t f_ops        f_pos 필드 변수에는 현재의 읽기/쓰기 위치를 저장




- 데이터 전달 함수

kernel 영역과 user 영역은 서로 접근하지 못하는 메모리 영역이기 때문에, pointer를 이용하지 못하고, 데이터를 전달하는 함수를 이용

 copy_from_user(void *to, void *from, unsigned long size)

 application으로부터 data를 복사한다.

 get_user(x, ptr)

 x 변수에 ptr의 사용자 메모리 값을 대입한다.





  • read 함수

- 주요 매소드

 ssize_t xxx_read (struct file *flip, char * buff, size_t count, loff_t *offp) 

 디바이스의 offp 위치에서 count 바이트만큼을 읽어서 사용자 영역인 buff로 저장해주는 기능



- 데이터 전달 함수

kernel 영역과 user 영역은 서로 접근하지 못하는 메모리 영역이기 때문에, pointer를 이용하지 못하고, 데이터를 전달하는 함수를 이용

copy_to_user(void *to, void *from, unsigned long size)

 application으로 data를 복사해준다.

 put_user(x, ptr)

 x 변수 값을 ptr의 사용자 메모리 값에 대입한다.





  • ioctl 함수


일반적으로 I/O Control에 관련한 작업을 수행하는 함수.

대부분의 ioctl 메소드 구현은 cmd 인수 값에 따라 올바른 동작을 선택하는 switch 문으로 구성한다.


- 사용자 영역에서 ioctl 함수 시스템 콜


int ioctl (int fd, int cmd, ...);



- 커널 영역에서 ioctl 함수


int (*unlocked_ioctl) (struct file *flip, unsiged int cmd, unsigned long arg);


- inode와 flip 포인터는 응용 프로그램의 파일 디스크립터 fd와 일치하는 인수

- cmd 인수는 명령을 나타내는 응용 프로그램의 인수 전달

- arg 인수는 명령 실행의 결과 데이터가 전달되는 unsigned long 형의 정수 또는 포인터


- cmd의 구성


총 32 bit로, 

[0:1] 읽기/쓰기 구분

[2:15] 데이터 크기

[16:23] 매직넘어

[24:31] 구분 번호



- cmd 명령의 해석 매크로 함수



 _IOC_NR

 구분 번호 필드 값을 읽는 매크로 

 _IOC_TYPE

 매직 넘버 필드 값을 읽는 매크로 

 _IOC_SIZE

 데이터의 크기 필드 값을 읽는 매크로 

 _IOC_DIR

 읽기와 쓰기 속성 필드 값을 읽는 매크로 



(예) if ( _IOC_TYPE(cmd) != MY_MAGIC ) return -EINVAL;



- cmd 명령의 작성 매크로 함수



 _IO 

 부가적인 데이터가 없는 명령을 만드는 매크로 

 _IOR

 데이터를 읽어오기 위한 명령을 작성 

 _IOW

 데이터를 써 넣기 위한 명령을 작성 

 _IOWR

 디바이스 드라이버에서 읽고 쓰기 위한 명령을 작성하는 매크로 



(예) _IOW (매직넘버, 구분번호, 정수형








디바이스 드라이버 (Device Driver) - 2. 디바이스 파일










2.1 디바이스 파일의 목적


  • Device Driver가 사용할 장치 파일을 만든다.
  • 유저가 Device Driver File을 이용해 장치를 사용할 수 있다.
  • 장치 파일을 생성하기 위해서는 mknod를 통해서 장치 파일을 생성한다.





2.2 디바이스 파일의 생성


  • mknod의 사용
- 일반적으로 /dev 경로에 생성한다.
- 장치 파일은 user와 Device Driver를 연결해주는 매개체이다.
- devfs에서 관리된다.


$ mknod /dev/name type major minor 



예) mknod /dev/SK c 252 0


  • 파일의 구성
- dentry 구조체 : 경로를 관리하는 구조체

- inode 구조체 : 파일을 관리하는 구조체

- 부모는 dentry.d_subdirs, 자식들은 dentry.d_child와 double linked list로 구성되어 있다.




2.3 디바이스 파일 동적 할당


  • udev
- 디바이스 파일을 자동으로 생성할 목적으로 만들어진 데몬

- /dev를 수동 생성 방법을 대체할 방법론
- 시스템 디바이스(sysfs)에서 정보를 취합할 API 제공

- udev는 sysfs와 밀접한 관계가 있으며, 각 디바이스의 탐지, 인식, 제거, 변경 등의 event를 커널 레벨에서 관리하기 위하여 hotplug 기능이 필수적이다.

- UNIX 계열의 device driver는 file 형태.

- kernel에 device driver가 loading하면 사용 가능 상태가 된다.

- Device driver에 사용하기 위해서는 반드시 /dev 밑에 device node file이 있어야 application이 접근 가능하다.


  • udev 탄생 배경

- /dev 밑에 디바이스 파일 노드를 만드는데 기존 방식의 문제 발생
- 너무나 많은 device node file를 만들어야 함
- kernel device model이 도입되면서 hotplug 관련 처리 문제를 해결할 수 있게 됨???


  • udev 데몬의 동작











- netlink를 통해 uevent를 udev 데몬에게 알린다.

netlink란?

커널과 유저 인터페에스 프로세스 사이에서 IPC를 하는 소켓과 비슷한(socket-like) 메커니즘 ?













캐릭터 디바이스 드라이버



1. 디바이스 타입



  • dev_t
major/minor 번호가 조합된 수형
major 번호와 쌍을 이뤄 하나의 장치 번호를 구성


- 매크로 (include/linux/kdev_t.h)

MAJOR(dev_t dev);         주번호 추출

MINOR(dev_t dev);         부번호 추출

MKDEV(int ma, int mi);    번호 설정



  • major 번호의 결정

- Manual Allocation

현재 사용 중이지 않은 major 번호를 알아낸 후 직접 하드 코딩함.

- Dynamic Allocation

alloc_chrdev_region() 함수를 이용하여 동적으로 할당

단점: 미리 디바이스 파일을 만들어 둘 수 없음
해결책: 장치 번호 등록 후 /proc/devices를 읽어 major 번호를 얻고, 장치 파일을 만드는 스크립트를 활용한다.




  • cdev 구조체

커널은 내부적으로 캐릭터 디바이스를 표현하기 위해 cdev 구조체를 사용
커널이 file operation에 등록된 디바이스 등록 함수 (open, read..)를 호출하기 전에 할당 및 등록하여야 한다.

<linux/cdev.h> 

 struct cdev{

      struct kobject kobj;

      struct module *owner;

      const struct file_operations *ops;

      struct list_head list; dev_t dev;

      unsigned int count;

}






2. 캐릭터 디바이스 드라이버 API



  • register_chrdev_region()

디바이스 개수를 포함한 디바이스 드라이버 등록 함수



- 함수


int register_chrdev_region (dev_t first, unsigned int count, char* name)



- 매개 변수


 first 

 할당 받으려는 디바이스 번호 범위 중 시작 번호.

 major/minor 번호를 모두 포함

 count

 minor 번호로 디바이스 개수

 *name

 디바이스 이름 (/proc/devices 에 나타난다)





  • alloc_chrdev_region()
디바이스 번호를 동적으로 할당하기 위한 함수

- 함수

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)

- 매개 변수

*dev

 성공적인 경우 디바이스 번호가 할당

 firstminor

 디바이스에 할당될 첫번째 minor number, 일반적으로 0

 count

 minor number로 디바이스 개수

 *name

 디바이스 이름(/proc/devices와 sysfs에 나타난다)





  • unregister_chrdev_region()
사용중인 디바이스 번호 해제

- 함수

void unregister_chrdev_region (dev_t first, unsigned int count)




  • cdev_init()
cdev 구조체 초기화

- 함수

void cdev_init (struct *cdev, struct file_operations *fops)

- 매개 변수

 *cdev 

 초기화할 cdev 구조체 

 *fops

 등록할 fop 구조체 포인터 





  • cdev_add()
준비된 cdev 구조체를 커널에 등록, 캐릭터 디바이스 등록

- 함수

void cdev_add (struct cdev *cdev, dev_t num, unsigned int count)

- 매개변수

 *cdev

 등록한 cdev 구조체

 num

 등록할 디바이스 번호 (major/minor 포함) 

 count

 등록할 디바이스 개수, 주로 1 






  • cdev_del()
등록된 캐릭터 디바이스를 제거.
디바이스 파일도 삭제해야 한다.
디바이스 번호도 삭제, unregister_chrdev_region() 이용

- 함수

int cdev_del (struct cdev *cdev)








디바이스 드라이버 (Device Driver) - 1. 개요


  • 시스템이 지원하는 하드웨어를 응용 프로그램에서 사용할 수 있도록 커널에서 제공하는 라이브러리
  • 응용 프로그램이 하드웨어를 제어하려면 커널에 자원을 요청하고, 커널은 요청에 따라 시스템 관리
    ..

  • 디바이스 드라이버의 역할
    • 디바이스 드라이버는 하드웨어를 사용가능하게 만들어 줄 뿐 하드웨어를 어떻게 사용할지에 대한 결정은 응용 프로그램에 넘겨야 한다.
    • 디바이스 드라이버는 온갖 상황을 고려해 균형있게 작성한다.

  • 리눅스 디바이스 드라이버의 공통적 특성
    • 커널 코드
      • 디바이스 드라이버는 커널의 한 부분이므로, 커널의 다른 코드와 마찬가지로 잘못되면 시스템에 치명적인 피해를 줄 수 있다.
    • 커널 인터페이스
      • 디바이스 드라이버는 리눅스 커널이나 자신이 속한 서브시스템에 표준 인터페이스를 제공해야 한다.
    • 커널 메커니즘과 서비스
      • 디바이스 드라이버는 메모리 할당, 인터럽트 전달, wait queue와 같은 표준 커널 서비스를 사용할 수 있다.
    • Loadable
      • 대부분의 리눅스 디바이스 드라이버는 커널 모듈로서, 필요할 때 로드하고 더 이상 필요하지 않을 때 언로드 할 수 있다.
    • 설정 가능 (Configuration)
      • 리눅스 디바이스 드라이버를 커널에 포함하여 컴파일할 수 있다.
      • 어떤 장치를 넣을 것인지는 커널을 compile할 대 설정할 수 있다.


  • 디바이스 드라이버 종류
    • 문자 디바이스 드라이버
      • 임의의 길이를 갖는 문자열이나 자료의 순차성을 지닌 장치를 다루는 디바이스 드라이버
      • serial, console, keyboard, printer, mouse 등이 있다.
    • 블록 디바이스 드라이버
      • 일정 크기의 버퍼(블록 단위)를 통해 데이터를 처리하는 디바이스
      • 커널 내부의 파일시스템에서 관리하고 내부적인 버퍼가 있는 디바이스 드라이버
      • 하드 디스크, 램 디스크 등이 있다.
    • 네트워크 디바이스 드라이버
      • 네트워크 계층과 연결되어 네트워크 통신을 통해 네트워크 패킷을 송수신할 수 있는 기능을 제공
      • 이더넷, PPP 등이 있다.


커널 모듈


  • 리눅스 커널이 부팅되어 동작중인 상태에서 디바이스 드라이버를 동적으로 추가하거나 제거할 수 있게 하는 개념.
  • 리눅스는 다른 운영체제와 달리 대부분의 기능을 모듈로 구현할 수 있다.
  • 커널의 한 부분으로 동작한다.

  • 모듈 프로그래밍
    • 모듈은 커널의 프로그램의 특징을 갖고 있으면서 커널에 등록, 해제할 수 있으므로 일반 프로그램과는 다른 소스 형식
    • 커널 라이브러리를 객체 형태(.ko)로 만들어서 시스템 콜을 통해서 리눅스 커널에 등록 요청 -> 커널은 해당 객체를 커널에 동적으로 링크
    • 커널 심볼 테이블
      • 커널 내부의 함수나 변수 중 외부에서 참조할 수 있는 함수의 심볼과 주소를 담은 테이블
      • 객체 형태로 작성된 커널 모듈참조할 커널 내부의 함수나 변수에 연결되어 동적으로 링크된다.
      • 코드에서 EXPORT_SYMBOL 매크로로 심볼 등록을 하면 보드의 /proc/kallsyms에서 등록된 심볼을 확인할 수 있다.


    • 커널 내 심볼의 시작 주소 / 심볼 타입 / 심볼 이름


  • 커널 모듈 vs 응용 프로그램

    • 응용 프로그램

      • exit으로 종료시 문제가 될 경우 시스템에 큰 영향을 미치지 않음 (kill)

      • 시스템콜을 사용하지 않고 API(Application Programming Interface)를 사용
        • UNIX에서는 POSIX을 표준으로 사용
        • POSIX: IEEE에서 OS를 위한 표준 인터페이스 규칙 ex) stdio.h, string.h
    • 커널 모듈
      • 등록 후 초기화 함수를 실행한 뒤 요청에만 응답
      • exit는 더 이상 요청을 받아들이지 않겠다는 의미
      • 유저 공간의 응용 프로그램이 직접 커널 코드를 호출하는 것은 불가능
      • 커널은 보호된 메모리 공간에 있으므로 시스템이 커널 모드로 전환되어야 한다.

    • 커널 코드와 유저 코드의 차이점
      • 커널은 커널 안에 구현된 라이브러리를 사용해야 한다.
            ex) lib/string.c -> linux/string.h
      • 커널은 GNU C로 작성된다.
      • 커널에는 유저 공간에서와 같은 메모리 보호가 쉽지 않다.
      • 커널은 고정 크기의 스택을 갖는다.


    • module를 사용하기 위한 명령
      • modutils 패키지에 포함된 명령
      • 명령
        설명
        insmod 모듈명.ko

        시스템에 모듈을 추가

        rmmod 모듈명

        시스템에서 모듈을 제거

        lsmod

        현재 등록된 모듈을 확인

 

  • 모듈의 기본 형태
    • 초기화 함수 
      • static int my_init(void);
    • 초기화 등록 매크로 함수
      • module_init(my_init);
      • 초기화 함수는 외부 코드에 의해 불려지지 않는다.
      • 초기화가 성공하면 0을 리턴한다.
      • 초기화는 대게 리소스를 등록하거나 자료구조를 생성한다.

  • 커널 모듈 적재 절차
    1. 주어진 모듈명을 ".ko" 인 파일에서 모듈명을 얻는다.
    2. /lib/modules/ 의 하부 디렉토리에서 해당 파일명을 찾는다.
    3. 읽어온 파일의 코드와 모듈명 그리고 module 구조체를 저장하는데 필요한 메모리 영역의 크기를 구한다.
    4. 구조체의 정보를 이용해 create_module() 함수를 호출한다.
    5. 이 함수는 모듈의 권한을 검사하고, fine_module() 함수를 이용해 이미 적재된 모듈인가를 검사한다.
    6. 만약 적재되지 않았다면, vmalloc() 함수를 호출해 새로운 모듈을 위한 메모리 영역을 할당한다.
    7. query_module() 함수를 통해 커널 심볼 테이블과 커널에 적재된 다른 모듈의 심볼 테이블을 구한다. (QM_MODULES, QM_INFO, QM_SYMBOL)
    8. 커널에 적재하려는 모듈의 프로그램 코드 주소를 재배치한다.
    9. 사용자 모드 주소 공간에 메모리 영역을 할당하고, 이곳에 모듈 구조체의 내용과 모듈명 그리고 재배치된 모듈 코드를 복사한다.
    10. 모듈 구조체의 init 필드의 함수 주소와 exit 필드의 함수 주소를 할당한다.
    11. 사용자 모드 주소 공간에 할당된 메모리 주소를 이용해 init_module() 함수를 호출한다.

 

  • 모듈 빌드하기
    • 커널 소스 트리에 있는 경우
      • 예시
        • Fish Master XL 2000을 위한 디바이스 드라이버(캐릭터)
        • drivers/char/ 의 Makefile에 다음을 추가
          • obj-m += fishing.o
        • drivers/char/fishing/ 안에 다음 내용을 포함하는 Makefile 작성
          • obj-m += fishing.o
        • 디바이스 드라이버 파일이 커 두 개로 나눠야 할 때
          • obj-m += fishing.o
          • fishing-objs := fishing-main.o fishing-line.o
        • 최종적인 fishing.ko가 만들어진다.

    • 커널 소스 밖에 있는 경우
      • 예시
        • Makefile을 다음과 같이 구성할 수 있다.
          • obj-m := fishing.o
          • fishing-objs := fishing-main.o fishing-line.o
        • 가장 큰 차이점은 build 과정이다.
          • make -C kernel/source/location SUBDIRS=$(PWD)
          • -C : 커널 소스 트리가 있는 위치
          • SUBDIRS : 드라이버 소스가 있는 작업 디렉토리

  • 모듈 설치
    • 컴파일된 모듈은 일반적으로 다음 위치에 설치
      • /lib/modules/version/kernel
      • 예: /lib/modules/2.6.10/kernel/drivers/char/fishing.ko
    • 일반적인 커널은 다음 빌드 명령으로 적절한 위치에 설치한다.
      • make modules_install
  • 여러개의 파일로 구성된 모듈 컴파일
    • Makefile 구성
      • start.c stop.c 코드를 가지고 모듈을 컴파일한다고 할 때

        Makefile 예시

        obj-m +=startstop.o
        startstop-objs := start.o stop.o


        KDIR := /work/ldd/linux-2.6.17.13-rebis


        all:

               make -C $(KDIR) SUBDIRS=$(PWD) modules

        clean:

               make -C $(KDIR) SUBDIRS=$(PWD) clean



  • 드라이버 매크로
    • 드라이버 정보 매크로
      • 매크로
        설명
        module_init()함수 변경 매크로
        module_exit()함수 변경 매크로
        __initinit 함수가 종료되었을 때, init 함수를 버리고, 메모리를 free로 만듦
        __initdata초기 변수에 대해 __init과 유사한 작업을 함
        __exit모듈이 커널 내로 삽입될 때 함수를 생략
        MODULE_LICENCE()GPL, GPL v2, Dual BSD/GPL, Proprietary 등
        MODULE_DESCRIPTION()모듈의 하는 일을 설명
        MODULE_AUTHOR()모듈 제작자
        MODULE_SUPPORTED_DEVICE()모듈이 지원하는 장치의 타입
    • 모듈 매개변수 매크로
      • 커널은 사용자가 매개변수를 부트시나 모듈 로드시에 선언하고 드라이버 안에서는 이 매개변수들이 전역변수처럼 보이도록 하는 프레임웍을 제공한다.

        매크
        module_param(name, type, perm)command 인자를 얻음. ex) ./insmod mynodule.ko myvariable=5
        module_param_array(name, type, nump, perm)

        인자: 변수명, 자료형, nump는 정수형 포인터, sysfs의 접근 권한.

        세번째 인자로 배열 개수를 포인터로 넘겨줌 (NULL 가능)

        module_param_string(name, string, len, perm);

        charp로 정의된 문자열을 지정할 때, len으로 길이와 함께 지정.

        길이는 sizeof(string) 형태로 사용 가능

        module_param_named(name, value, type, perm);변수의 값(value)를 내부에 포함 시키고자 할 때 사용
        MODULE_PARM_DESC()

        모듈이 가질 수 있는 문서 인자들로 사용.

        인자: 변수 이름, 변수를 묘사하는 문자열


  • 커널 메세지 출력
    • printk()
      • printf()와 유사하지만 커널의 메시지를 출력하고 관리할 수 있는 특성이 있다.
      • 메세지 기록 관리를 위한 로그 레벨의 지정
      • 출력 디바이스의 다중 지정
      • 콘솔에서 확인하거나 dmesg 명령을 이용해서 로그 파일 확인
      • 로그레벨 지정
        • 로그 레벨은 printk() 함수에 전달되는 문자열의 선두 문자에 "<1>"과 같이 숫자로 등급을 표현한다.
        • linux/kernel.h 에 정의된 선언문을 이용하는 것이 바람직하다.
        • 상수 선언문
          의미
          #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과 같은 레벨이다.
        • 다음의 처리 결과는 모두 같다.
          • printk(KERN_WARNING "system ok\n");
          • printk("<4>" "system ok\n");
          • printk("<4> system ok\n");
          • printk("system ok\n");
        • printk() 사용시 주의점
            • 개행 문자가 있어야 출력을 시작한다.

    • 커널 메시지 관리 데몬
      • klogd: 커널에서 발생하는 메시지를 기록하고 관리한다.
      • syslogd: 커널에서 발생하는 메시지와 응용 프로그램에서 요청한 시스템 정보를 기록하고 관리한다.
      • /proc/kmsg
        • 커널 메시지가 발생할 때마다 관찰할 수 있다.
        • cat /proc/kmsg
        • dmesg

 


출처 http://blog.naver.com/PostView.nhn?blogId=yyg1368&logNo=60121733093

출처 https://wiki.kldp.org/Translations/html/The_Linux_Kernel-KLDP/tlk12.html

출처 https://www.joinc.co.kr/w/Site/Embedded/Documents/LinuxKernelModuleProg

 

 

디바이스 드라이버를 위한 커널 서비스



2. 인터럽트 처리




2.1 예외 처리


 외부의 정상적인 요청이나 비정상적인 오류의 발생에 의해서 프로그램을 중지하고 발생된 요청이나 오류를 처리하는 것

 예외처리가 발생하면 처리해야 할 프로그램의 위치가 메모리의 특정한 위치에 지정


 예외 벡터 테이블

 address

Exception 

Mode on Entry 

Priorities

0x00000000

Reset 

Supervisor 

1 

0x00000004

Undefined instruction 

Undefined 

0x00000008 

Software interrupt 

Supervisor 

0x0000000C 

Abort (prefetch) 

Abort 

0x00000010 

Abort (data) 

Abort 

0x00000014 

Reserved 

Reserved 

Reserved 

0x00000018 

IRQ 

IRQ 

0x0000001C 

FIQ 

FIQ 






2.2 인터럽트



디바이스는 프로세서와는 다른 시간 간격, 정확하게는 훨씬 느린 시간으로 수행된다.


프로세서가 무작정 외부 이벤트를 기다리는 것은 바람직하지 못하므로 프로세서에게 뭔가가 발생했다고 알릴 방법이 있어야 한다.





2.3 인터럽트 컨트롤러



외부에서 발생하는 여러가지 인터럽트를 받아들여 서비스 여부를 결정.

발생된 인터럽트에 대한 정보는 처리가 완료될 때까지 저장


인터럽트 컨트롤러 레지스터

 이름

용도

 SRCPND

 인터럽트가 발생 여부가 체크 되는 레지스터, 여러개의 인터럽트 소스가 체크될 수 있음.

 INTMSK

 인터럽트의 사용 여부를 결정

 INTPND

 SRCPND 레지스터와 달리 여러개의 인터럽트 소스가 발생하면 우선 순위 로직을 거쳐 하나의 인터럽트 소스만 체크되는 레지스터 

 SUBSRCPND

 SRCPND 레지스터와 동일한 기능이며, Sub 개념을 좀 더 많은 인터럽트 소스를 처리하기 위해 사용 

 INTSUBMSK

 INTMSK 레지스터와 동일한 기능이며, SUBSRCPND의 인터럽트 사용 여부를 결정

 INTMOD

 인터럽트 소스를 IRQ/FIQ로 사용할지를 결정 






2.4 인터럽트 처리



  • 인터럽트 핸들러 (인터럽트 서비스 루틴 - ISR)

인터럽트를 처리하는 커널 함수.
각각의 디바이스가 발생시키는 인터럽트에는 그와 관련된 인터럽트 핸들러 존재
디바이스 드라이버의 일부분
인터럽트 컨텍스트에서 실행 (인터럽트 모드로 전환)



  • 인터럽트 처리를 두 단계로 분리

- 톱 하프 (top half)

: 인터럽트 핸들러
  인터럽트가 발생하는 즉시 실행되는 타임 크리티컬한 작업들


- 바톰 하프 (bottom half)

: 나중에 해도 무관한 작업들
  모든 인터럽트를 활성화시킨 상태에서 실행 (보통 인터럽트 핸들러가 리턴되면 실행)




  • 인터럽트 서비스 처리 흐름

프로세서마다 인터럽트 처리 방식이 다르기 때문에 리눅스 커널에서는 동일하게 처리하기 위해 IRQ 인터럽트는 모두 do_IRQ() 함수를 호출하여 처리






1. request_irq() 함수를 이용하여 처리하고자 하는 IRQ 번호와 서비스 함수 주소를 등록


2. 인터럽트 발생 후

2.1 아키텍처마다 고유의 IRQ 검출 루틴을 이용하여 발생된 인터럽트의 IRQ 번호를 획득하여 do_IRQ() 함수 호출
2.2 do_IRQ() 함수는 irq_desc 전역 변수에서 IRQ 번호에 맞게 등록된 인터럽트 서비스 함수를 찾고, 있으면 호출
2.3 참조된 데이터의 주소나 값들은 인터럽트 서비스 함수의 매개 변수인 dev_id에 전달
2.4 인터럽트 처리가 필요 없는 경우는 free_irq() 함수 호출
      -> irq_desc 전역 변수에 등록된 인터럽트 서비스 함수를 제거




  • 인터럽트 서비스 함수의 형식

     - 매개 변수

 int irq 

 인터럽트 번호 (irqs.h에 정의) 

 void *dev_id 

 인터럽트 ID 또는 인터럽트 함수가 사용 가능한 메모리 주소.

 인터럽트 서비스 공유에 사용되거나 인터럽트 서비스 함수를 수행할 때 필요한 정보가 저장된 메모리의 주소를 저장

 struct pt_regs *regs

 인터럽트가 발생했을 당시의 레지스터 값

 대부분 사용하지 않음 (디버깅 목적)



- 주로 사용하는 메모리 할당 및 해제

kalloc()

- GFP_ATOMIC 인자를 사용한 방식만 사용해야 함
- vmalloc 함수로 인터럽트가 발생하기 전 미리 할당한 메모리는 아무런 제약 사항이 없음


- 인터럽트 함수 실행 후 정상적으로 처리되었다면 IRQ_HANDLED를 반환




  • 인터럽트 서비스 함수의 등록 - request_irq()

- 매개 변수

 int irq

 인터럽트 번호

 irqreturn_t (*handler) (int, void* struct pt_regs *regs)

 인터럽트가 발생했을 때 처리되는 인터럽트 서비스 함수 포인터 

 unsigned long flags

 인터럽트 공유나 바른 인터럽트 같은 처리 속성을 지정

 SA_INTERRUPT: 다른 인터럽트를 허가하지 않음

 SA_SHIRQ: 동일한 인터럽트 번호를 공유

 SA_SAMPLE_RANDOM: 랜덤 값 처리에 영향을 줌 (/dev/random) 

 const char *device

 인터럽트에 대한 소유자를 표현하는 문자열을 지정 

 void* dev_id

 인터럽트가 공유되었을 때 인터럽트에 대한 구분 인자로 사용

 인터럽트 서비스 함수가 참조하는 데이터에 대한 주소를 지정



- 정상적으로 수행되면 0을 리턴

- 비정상적일 때는 에러 상황에 맞는 음수 값을 반환




  • 인터럽트 서비스 함수의 해체 - free_irq()

- 매개 변수

 int irq 

 제거하고자 하는 인터럽트 서비스 함수의 인터럽트 번호

 dev_id

 request_irq() 함수에서 사용한 dev_id와 동일






  • 인터럽트 서비스 등록 시점과 해제 시점


- 하나의 응용 프로그램만이 디바이스 파일을 사용하는 경우


모듈 초기화, open() 함수 모두 ISR 등록 가능

응용 프로그램이 종료되고 난 후 close() 함수에서 ISR 제거


- 두 개 이상의 응용 프로그램이 디바이스 파일을 사용하는 경우


모듈 초기화에서 ISR 등록

-> open() 에서 등록할 경우 동일한 인터럽트 서비스 루틴이 두 번 이상 등록할 수 있음

-> open() 에서 등록할 경우 먼저 해제를 시도한 쪽에서 인터럽트 서비스 함수를 제거하는 경우,

    다른 쪽 응용 프로그램에서 인터럽트 서비스 함수가 동작하지 않음.



- PC 같은 범용 시스템에 사용되는 디바이스 드라이버

-> open()과 close() 함수에서 처리



- 임베디드와 같은 특정 목적의 시스템에서는 모듈의 등록과 해제 시에 처리하는 것이 좋음





2.5 인터럽트 함수와 디바이스 드라이버 간의 데이터 공유


인터럽트 서비스 함수는 프로세스 문맥과 별개로 동작하므로 인터럽트 서비스 함수와 디바이스 드라이버 함수 간에 데이터를 공유하는 방법이 필요

 <공유 방법>

  1. 전역 변수
인터럽트 하나에 인터럽트 서비스 함수 하나가 동작하는 경우

  2. dev_id 매개 변수
여러 디바이스를 하나의 인터럽트 서비스 함수에서 사용 가능
다중 프로세스 환경에 매우 적합






2.6 인터럽트 공유


같은 인터럽트 번호에 대해 다른 인터럽트 서비스 함수 등록 가능

-> request_irq() 함수의 매개 변수에서 플래그를 SA_SHIRQ로 설정
    dev_id 매개 변수의 경우, 동일한 인터럽트 서비스 함수를 구별하기 위해 0이 아닌 값을 사용해야 함


같은 인터럽트에 대해 여러 인터럽트 서비스 함수가 동작할 경우

-> 인터럽트 서비스 함수는 자신이 관리하는 디바이스에서 인터럽트가 발생했는지를 확인하는 과정이 필요

인터럽트가 들어오면 -> 자신의 디바이스에서 발생한 것인지 체크 후 -> 자신의 디바이스가 아니면 IRQ_NONE 리턴,
맞으면 IRQ_HANDLED 리턴



2.7 인터럽트 금지와 해제


    • 인터럽트 서비스 함수가 동작 중에 다른 인터럽트가 발생하지 못하게 금지하는 방법

:request_irq()의 플래그를 SA_INTERRUPT를 포함


커널이 프로세서의 인터럽트를 금지 시킨 후 호출

인터럽트 두 개가 동시에 발생하면 SA_INTERRUPT가 설정된 서비스 함수를 먼저 수행한다.



    • 일반적인 함수 수행 중에 데이터 처리를 보호하기 위해 인터럽트를 강제로 막는 방법


#include <asm/irq.h>

 void disable_irq(int irq)

 인터럽트 금지

 void enable_irq(int irq)

 인터럽트 허가

 void disable_irq_nosync(int irq)

 현재 핸들러가 종료할 때까지 기다리지 않음

 synchronize_irq(unsigned int irq)

 특정 인터럽트 핸들러가 실행 중일 경우 기다렸다가 실행이 끝나면 리턴






    • 프로세서 전체의 인터럽트를 금지하고 해제하는 방법

#include <asm/system.h>

 local_irq_disable(void)

 프로세서의 인터럽트 처리를 금지

 local_irq_enable(void)

 프로세서의 인터럽트 처리를 허가

 local_save_flags(ulong flags)

 현재의 프로세스의 상태를 저장

 local_restore_flags(ulong flags)

 저장된 프로세스의 상태를 복구

 local_irq_save(unsigned long flags)

 프로세서의 인터럽트 처리를 금지시키면서 동시에 현재 프로세서의 상태를 저장

 local_irq_restore(unsigned long flags)

 프로세서의 인터럽트 처리를 허가시키면서 동시에 현재 프로세서의 상태를 복구




    • 보편적인 인터럽트 방지법

unsigned long flags;


local_save_flags(flags);

local_irq_disable();

local_restore_flags(flags); 



* local_irq_enable() 함수를 사용하지 않는 이유


local_save_flags() 함수에 의해 저장된 flags 변수에 이미 인터럽트 허가 상태가 포함되어 있기 때문에 local_restore_flags() 함수에 의해 local_irq_enable() 함수를 호출한 효과가 발생












디바이스 드라이버를 위한 커널 서비스







1. 변수와 메모리 할당



1.1 디바이스 드라이버에서 변수의 사용

    • 지역 변수와 전역 변수 중에 지역 변수를 사용하는 것이 좋다.

      -> 디바이스 드라이버는 다중 프로세스 환경에서 동작.
          프로세스 간 변수 사용에 따른 경쟁 문제를 해결하기 위해 가급적 지역 변수를 사용하는 편이 좋다.

    • 변수 명 사용시 static을 주로 사용

      -> 커널 소스는 방대한 함수와 전역 변수가 있으므로 이것이 서로 중복되지 않도록 static으로 선언
          cf. 함수 안에 static을 쓰면 전역 변수처럼 되기 때문에 함수 안에 쓰지 않는다.




1.2 이식성과 데이터 형


    • 데이터 형


다른 플랫폼에서 문제없이 수행되려면 변수의 데이터 형을 명확히 해야한다.


특히, int 형의 경우, "CPU의 레지스터와 동일한 크기를 가지는 타입"이다.


이 말은 int의 실제 크기는 플랫폼마다 달라진다는 얘기이다.




따라서 커널에서 제공하는 데이터형을 사용하는 것이 바람직하다.




 #include <asm/types.h>에서 제공하는 데이터형

 부호 있는 정수

부호 없는 정수 

 __s8, s8

 8 bits (byte)

 __u8, u8

 8 bits (byte) 

 __s16, s16

 16 bits (word) 

 __u16, u16

 16 bits (word) 

 __s32, s32

 32 bits

 __u32, u32

 32 bits

 __s64, s64

 64 bits

 __u64, u64

 64 bits






    • 구조체의 실제 저장 

구조체는 대부분 int형의 배수로 선언이 된다.

구조체 안에 char, int 자료형 두 개가 있다고 가정했을 때, 실제 크기는 int형 4 byte + char형 1 byte라고 생각하기 쉽지만,
실제로는 4 byte + 4 byte로 구조체의 크기는 총 8 byte


정확한 데이터 교환을 위해 packed 라는 키워드를 사용해 실제 변수 크기로 선언되도록 하는 것이 좋다. 


typedef struct

{

       u16 index;

       u16 data;

       u8   data2;

}__attribute__ ((packed)) testctl_t;






    • 바이트 순서


      다른 플랫폼에 이식할 경우, 정수 바이트 순서에 주의해야 한다.

      프로세스에 따라 바이트 순서를 처리하는 헤더는 include/linux/byteorder/ 디렉토리에 있다.

      #include <asm/byteorder.h>의 헤더를 포함시켜 사용한다.

      리틀엔디언 방식인지, 빅엔디언 방식인지에 따라 __LITTLE_ENDIAN , __BIG_ENDIAN 이 정의된다.






    • 동적 데이터 접근


      소스 코드에 정의 된 데이터 구조는 아키텍처에 따라 다르게 컴파일될 수 있다.

      예를 들어, 소스 코드에 int char int 순서로 정의를 해놨다고 해도 컴파일하고 나면 아키텍처에 따라 순서가 바뀌어 char int int 혹은 int int char 로  정렬되어 컴파일 될 수 있다.

      크기가 정해져 있지 않은 데이터를 액세스해야하는 경우 예외 처리기에서 처리되어 정렬된 데이터에 접근해야 하기 때문에 성능이 크게 저하된다.

      따라서 크기가 정해져 있지 않은 데이터에 액세스해야하는 경우 다음 매크로를 사용해야 한다.


    •  #include <asm/unaligned.h>

       get_unaligned (ptr) 

       ptr 주소에서 값을 읽는다.

       put_unaligned (val, ptr)

       ptr 주소에 val 값을 쓴다. 








    • I/O 메모리 접근 변수 처리


      메모리 번지를 이용하는 I/O처리는 최적화 등에 의해 의도되지 않은 형태로 변할 수 있으므로 주의를 기울인다.


          (예시)
          0xE0000300 번지 (센서 입력)이 바뀌는 것을 대기하기 위해

    u32    *ptr = (u32 *) 0xE0000300;

     while (*ptr = *ptr);
     Led_on();



            위 코드는 컴파일러에 의한 최적화로 다음과 같이 변할 수 있다.

    u32    *ptr = (u32 *) 0xE0000300;

     while(1);



            이 때는 voltaile를 이용하여 최적화되지 않도록 한다.


    volatile u32    *ptr = (u32 *) 0xE0000300;








    1.3 커널에서의 동적 메모리 관리




    커널에서는 malloc()이나 free()를 이용해 동적 메모리를 사용할 수 없다. 디바이스 드라이버는 커널 메모리를 사용하므로 물리적 접근이 가능하며 MMU를 고려한 처리가 필요하다.


        * 가상 메모리 (Virtual Memory)
             - 프로그램이 실행될 때 반드시 프로그램 전체가 실제 메모리에 있을 필요는 없으므로 현재 실행되어야 하는 부분만이 실제 메모리에 옮겨 쓰는 것이 가상 메모리

             - 이 가상 메모리를 사용하기 위해 주소 변환을 하는 장치가 필요한 데 이 장치가 MMU











    커널에서의 동적메모리 할당 함수

    함수 

    특징 

     kmalloc(), kfree()

     malloc(), free()와 유사하며 디바이스 드라이버에서 많이 사용

     할당 속도가 빠른 것이 특징
     할당 가능한 크기: 32*PAGE_SIZE

     vmalloc(), vfree()

     할당하려는 크기 외에는 매개변수를 주지 않음

     크기 제한이 없음

     가상 메모리 관리 루틴이 수행되기 때문에 kmalloc보다 속도가 느림 

     인터럽트 서비스 함수 안에서는 사용 불가

     __get_free_pages(), free_pages()

     페이지 단위 할당 함수







출처

https://ko.wikipedia.org/wiki/Volatile_변수

https://dojang.io/mod/page/view.php?id=432

http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=968

http://l2men.tistory.com/21

http://www.xml.com/ldd/chapter/book/ch10.html

http://young3276.tistory.com/4


    



리눅스 커널


운영체제란, 커널과 디바이스 드라이버, 커맨드 쉘 혹은 유저 인터페이스, 그리고 기본적인 파일들과 시스템 유틸리티를 포함한다.
     커널은 시스템의 다른 모든 부분을 위한 기본적인 서비스를 제공하고, 하드웨어를 관리하며, 시스템 자원을 나눠준다.


커널은 5개의 기능 블럭으로 구분한다.

      • 프로세스 관리
      • 메모리 관리
      • 파일 시스템 관리
      • 디바이스 관리
      • 네트워크 관리






커널 코드의 구성은 다음과 같다.


디렉토리 

내용 

 arch

 CPU 종속적인 코드

 ARM, MIPS와 같이 프로세서마다 독자적인 하위 디렉터리 존재

 crypto

 암호화 API

 Documentation

 커널 하위 시스템에 대한 간략한 설명 

 drivers

 장치 드라이버와 주변 장치 컨트롤러

 fs

 파일 시스템 구현과 관련된 코드

 include

 커널 헤더 파일

 init

 커널 시작 및 초기화 관련 코드

 ipc

 프로세스 간 통신 관련 코드 ex. 메시지 큐, 세마포어, 공유 메모리

 kernel

 스케줄러, 인터럽트 처리, 락킹 등 기본 커널에서 아키텍처 중립적인 부분이 구현

 lib

 일반적인 Hashing, CRC (Cyclic Redundancy Code) 계산 함수 같은 라이브러리 루틴

 mm

 메모리 관리 구현 코드

 net

 IPv4, IPv6, 블루투스와 같은 네트워크 프로토콜

 scripts

 커널 빌드에 사용할 스크립트

 security

 보안용 프레임워크

 sound 리눅스 오디오 하위 시스템

 usr

 초기 유저 공간 코드




사용자 영역과 커널 영역

        • 사용자 영역

          • 응용 프로그램이 동작하기 위해 사용되는 메모리 공간
            • stack 영역
               - 프로그램 시행시 지역 변수나 환경 변수와 같은 변수들이 위치
            • heap 영역
               - 동적 메모리 할당 영역
            • bss 영역
               - 초기화 되어 있지 않은 전역 변수나 정적 변수들이 저장.
               - 로더에 의해 메모리로 적재될 때 공간을 확보한 후, 0으로 초기화
            • data 영역
               - 초기화되어 있는 전역 변수들이 저장
            • text 영역
               - 프로그램 코드(함수나 일반 처리문 등)와 읽기 전용 데이터(상수)로 이루어짐

        • 커널 영역

          • 운영체제가 실행되기 위해서 필요한 메모리 공간



커널에 자원 처리를 요청하는 방법 

        • 시스템 콜 방식
          • 소프트웨어 인터럽트
          • 응용 프로그램으로 하여금 하드웨어나 다른 운영체제 리소스를 접근할 수 있게 해준다.
시스템콜에 대한 이미지 검색결과


        • 시스템 콜 핸들러

          • 유저 공간의 응용 프로그램이 직접 커널 코드를 호출하는 것은 불가능

          -> 커널은 보호된 메모리 공간에 있으므로 시스템이 커널 모드로 전환되어야 함


          • 소프트웨어 인터럽트
            • 유저공간에서 커널 모드로 진입하기 위해 exception이 발생하면 시스템은 커널 모드로 전환되어 exception handler를 실행한다.
            • 이 핸들러가 시스템 콜 핸들러가 된다.


          • 시스템 콜 핸들러 open() 수행과정
            1. test.c main()에서 open() 호출
            2. libc.a에서 open()
              시스템콜의 고유번호를 eax 레지스터에 저장
              SW 인터럽트 발생
            3. ARM에서 system_call() 명령이 저장된 vector table 참조
            4. vector_swi()에서 open() 명령의 번호를 참조하여 sys_open() 호출
            5. sys_open()은 dev_open()을 호출하면서 실제 open  명령을 수행



리눅스 부팅 과정

      1. Power on
      2. ROM-BIOS 프로그램 실행

            • BIOS (Basic Input/Output System): 메모리의 특정 번지로 자동 로드되어 실행됨
            • POST(Power On Self Test) 수행

                  • 장착된 H/W가 인식됨. (예: 시리얼 장치, 마우스 장치, 사운드 장치)
                  • H/W의 물리적인 손상체크와 초기화가 병행
            • 부팅 매체 (Disk, CD_ROM 등) 검색
            • MBR(Master Boot Record)를 읽어, 부트로더(GRUB)를 로드시킨다.
      3. GRUB  실행(부트로더 실행)

            • 부팅메뉴 선택
              • /boot/grub/grub.conf 파일
              • kernel 이미지 로드
              • swapper 프로세스 호출
      4. swapper 프로세서 (PID=0)

            • 커널 이미지 압축을 해제
            • 각 장치 드라이브들을 초기화
            • 루트( / ) 파일 시스템을 read-only로 마운트
            • 파일시스템 검사
            • 루트( / ) 파일시스템을 read-write 모드로 마운트
            • init 프로세스 (PID=1)
      5. init 프로세스 (PID=1)

            • 리눅스에서 모든 프로세스는 그 실행과 함께 설정파일을 읽어들인다.
            • init 프로세스는 /etc/inittab이라는 설정 파일을 읽어들여 무엇을, 어떻게, 언제 실행할 것인가를 결정하여 커널 서비스 시작




출처 http://cheonee.tistory.com/entry/리눅스-커널과-디바이스-드라이버

출처 http://cherub.sungkyul.edu/~web/jinboard/files/63_boot.pdf

출처 http://www.linuxlab.co.kr/docs/5-2.htm

출처 http://yongho1037.tistory.com/563

출처 http://young3276.tistory.com/4

 


+ Recent posts