디바이스 드라이버 (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

 

 

+ Recent posts