DEVELOP

임베디드 시스템 소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.


Linux Device Drivers


Device Driver

Network Device Driver

  • 네트워크 프로토콜 스택과 상호 작용 
  • 소켓 인터페이스 
  • ex) 이더넷 드라이버

Block Device Driver

  • 랜덤 엑세스 장치 
  • open, close, read, write 시스템 콜
  • 파일에 불특정한 위치로 포인터를 옮김
  • ex) 스토리지 드라이버 

Character Device Driver

  • sequential access device (순차적 엑세스 장치) : 바이트 스트림으로 엑세스 
  • block device driver와 동일한 인터페이스(open, close, read, write)
  • 랜덤 access 불가
  • 어떤 데이터를 바이트 스트림으로 아웃풋 디바이스에 주거나 인풋 디바이스에서 바이트 스트림을 받기 가능 
  • ex) 센서 드라이버 

Applications' Point of View

  • 파일은 스토리지에 저장되어 있는 정보 집합체가 아닌 디바이스들에 대한 abstraction
  • 파일로 응용에게 전달
  • 응용은 file Operations 통해 접근 가능 
  • 어떤 io디바이스 자체를 파일로 abstration했다고 이해해야 함 

Driver Programming

registering and Initializing Drivers

Network devices

  • net_device, net_device_ops 구조체 초기화
  • register_netdev()로 등록

Block devices

  • getdisk, block_device_operations 구조체 초기화
  • add_disk()로 등록

Character device

  • cdev, file_operations 구조체 초기화
  • cdev_add()로 등록

File Operations 

- cdev 구조체와 file_operations 구조체를 초기화해주는 것이 중요하다. 

- Character Device Driver는 커널 모듈로서 만들어진다.

- 응용(App)이 file operation을 system call하고, virtual file system을 거쳐서 device driver에 실제 구현한 file operation들이 호출되면서 HW Device에 접근하게 된다. 

- cdev 구조체는 character device의 전체 정보를 포함하고 있는 가장 상위에 존재하는 구조체 

- ops라는 포인터는 file_operations를 가리키고 있다. 

- file_operations는 open, read, write 등의 function pointer를 저장하고 있는 구조체 

cdev 구조체

: 하나의 캐릭터 디바이스를 표현하기 위해 최상위에 존재하는 구조체 

  • include/linux/cdev.h 에 정의되어 있다. 
  • ops
    • 해당 character device 안에 있는 함수들의 실제 구현을 포함 
    • my_cdev->ops =&my_ops; 또는 cdev_init(my_cdev, &my_ops); 로 초기화 
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

file_operations 구조체 

: 실제 파일 집합의 포인터 구조체
  • include/linux/fs.h에 정의
  • read, write 등의 함수 포인터 집합
  • 구현할 필요가 없는 것은 null 포인터로 두면 된다.
  • 모든 함수를 정의하고 초기화할 필요는 없다. 
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*unlocked_ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
int (*open) (struct inode *, struct file *);
};

open()

: 해당 파일에 대한 descripter 넘겨준다.

  • null값이라면, 항상 성공했다는 반환값을 주고 있다. 

release()

: close했을 때 불려지는 함수를 연결해준다. 

read()

: 디바이스로부터 데이터를 읽어온다.

  • 성공 : 몇바이트 읽어왔는지 양수값을 리턴한다.
  • 실패 : 오류값 -EINVAL (음수)을 리턴한다. 

write()

: 디바이스에 데이터를 보내기 위한 함수이다. 

  • 성공 : 몇바이트 읽어왔는지 양수값을 리턴한다.
  • 실패 : 오류값 -EINVAL (음수)을 리턴한다. 

ioctl()

: io control의 약자 

  • 어떤 구현을 할 것인지 정해진 것이 없다.
  • 임의의 디바이스에 대해 특화된 기능이 필요할 수도 있음을 미리 알고 구현하는 것은 어렵다. 
  • 꽤 많은 파일 오퍼레이션이 정의되어있지만 미리 정의되어있지 않은 경우도 있다. 
  • 기능이 하나가 아니라 여러개가 있을 수 있다.
  • flad/cmd (unsigned int)
    • 인자값으로는 falg/cmd를 전달받도록 되어 있다. 
      ( 번호에 맞는 각각의 기능을 수행해야 한다.)
  • parameter (unsigned long)
    • 파라미터가 필요없을 수도 있고, 변수일수도 있고, 함수나 구조체일 수도 있다. 
    • 인자값이 하나라면 직접 전달한다.
    • 여러개 또는 구조체 전달시에는 메모리 공간의 주소번지를 전달한다.  
  • cmd 시 유의할 점 
    • 커맨드 정의 시 기존에 사용하던 것과 같은 것 쓰면 문제가 발생할 수 있다.
    • 많은 경우 임의의 번호를 사용헤도 되지만, 기본적인 룰이 존재한다. 
    • Documentation/ioctl/ioctl-number.txt 에서 사용하고 있지 않은 magic number를 사용해야 한다. 
    • 보통 switch 형태로 구현된다. 
  • poll()
    • 서버 프로그래밍에서, 여러 파일 디스크립터들을 select, poll, epoll에게 넘겨주었을 때, read / write가 준비된 것들을 리턴해 준다. 
    • 비동기 통신 잘되도록 하기 위한 것이다. 

registration

  1. 함수 구현
  2. 함수 포인터와 연결
  3. cdev의 ops와 연결

cdev_add()

: 커널에게 이 커널 모듈이 하나의 캐릭터 디바이스 드라이버라고 이야기해주는 함수, 캐릭터 디바이스 드라이버를 운영체제에 등록해주는 과정  

  • 커널 함수, export할 수 있기 때문에 커널 모듈에서 호출 가능 
  • struct cdev *dev
    • 캐릭터 디바이스의 모든 정보를 포함하고 있는 최상위 구조체 
    • ops가 가리키는 것이 file operations 구조체 
  • dev_t num
    • 해당 캐릭터 디바이스의 고유넘버로서 id같은 것 
    • major number와 minor number로 이루어져 있음 
  • unsigned int count
    • 몇개까지의 디바이스 드라이버가 있냐를 명시

Major and Minor Numbers

Major Number

: 특정 디바이스를 제어하는 드라이브의 고유 번호 

  • 제품마다 정해져있지는 않고 사용하지 않는 것을 그때그때 할당한다.
  • 사용하지 않는 번호를 동적 할당하도록 한다. 
  • alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
    • 사용하지 않는 major number를 얻기 위한 커널함수
    • dev : 결과값
    • baseminor : 요청된 minor number의 시작주소, 일반적으로 0
    • count : 요청된 minor number의 수
    • name : 연결된 디바이스 또는 드라이버의 이름 , 고유 이름 

Minor Number

: 여러 드라이버에 의해 제어되고 있는 드라이버가 여러개 있을 때 그들 사이에서 구분하기 위한 고유번호 

  • 동종의 디바이스 구분 가능 

ls-l/dev

 

  • 담당하고 있는 드라이버의 major, minor nummber 정보들은 inode에 들어가있다.  (read 함수)
  • file 구조체가 가리키고 있는 f_inode에 접근할 수 있고, 그 inode에 major, minor number 정보들이 들어있다. (write 함수)
  • imajor() , MAJOR()
    • inode를 인자값으로 받아서 major number를 리턴 
  • iminor() , MINOR()  
    • inode를 인자값으로 받아서 minor number를 리턴

Initialization of Device Drivers

  • insmod를 하면 init 함수가 호출되면서 관련된 초기화 과정을 거치게 된다. 
  • 대부분의 초기화 과정은 init 함수에 들어가있다. 
  1. cdev & file_operations 초기화
  2. major & minor number 얻어옴
  3. cdev_add 커널 함수 호출하여 cdev 구조체를 등록해주어야 함 

Device Files

응용 프로그램과 연결되는 고리가 무엇인가

Devicd Files

: 시스템 내 존재하는 io 장치들을 추상화한 것 

  • 응용 프로그램에서 io 장치 제어시 input/output 요구할 때 file io system call 호출해서 디바이스 제어 가능 
  • 디바이스 파일들은 /dev 디렉토리에 존재 
  • 디바이스 파일은 디바이스 노드 (device node)라고 불리기도 한다.
  • mknod
    • : 디바이스 파일 생성 명령어
  • 디바이스 파일을 만들 때에는 항상 major number 명시해주어야 한다.   
  • alloc_chrdev_region()
    • : major number 할당
  • /proc/devices
    • : major number 할당 확인 가능 

/proc/devices

  • major number는 그때그때 사용하지 않는 번호를 동적으로 할당한다.
  • 디바이스 드라이버를 받을 때마다 major number가 달라질 수 있다. 
  • 매번 major number를 확인해주어야 한다. 
  • 자동으로 감지해서 mknod하는 방법이 일반적이다. 
#!/bin/sh
MODULE=“mymod”
major=$(awk “\$2==\“$MODULE\” {print \$1}” /proc/devices)
mknod /dev/${MODULE}0 c $major 0
  • MODULE
    • : 모듈 이름 
  • awk
    • : 특정 파일을 컬럼별로 구분하도록 했을 때 원하는 것을 빼오는 명령어 
  • mknod
    • 디바이스 파일 만들기위한 명령어
    • c : 캐릭터 디바이스 
    • 루트 권한 있어야 함 

Put Things Together

  1. 커널 모듈 안 cdev 구조체 초기화 
    (ops라는 포인터는 file operation 구조체 가리킨다. )
    (file_operation은 open, read, write 등의 함수의 실제 구현 부분 포함)
  2. major number 얻기 위해 alloc_chrdev_region 호출, 커널에서는 사용되지 않고 있는 major number를 리턴해준다.
  3. /proc/devices 안에 major number에 대한 정보 저장 
  4. 커널에서 cdev_add 호출하며, 초기화했던 캐릭터 디바이스 드라이버에 대한 정보를 등록 
    (major number와 연결)
    (운영체제 안에 캐릭터 디바이스 드라이버가 제대로 등록된 것)
  5. /proc/devices에서 이름으로 major number를 얻어와 해당 major number로 mknod
  6. device file 생성 
  7. device file open
  8. open system call 은 file descriptor 리턴 
  9. 해당 file desc로 read/write 호출 시 file descriptor 통해 inode 얻고, inode 기반으로 major number 얻고, major number 기반으로 해당 device driver를 찾고, cdev 구조체 접근하고, cdev 안 file ops 구조체 접근 
    -> 실제로 구현했던 file operation의 바디 부분에 해당하는 함수를 초훌 
  • 디바이스 파일과 디바이스 드라이버 연결고리는 Major number와 Minor number이다. 

Parameter Passing

모듈 변수의 값을 지정할 수 있는 방법

  • 모듈을 커널에 삽입하는 명령어 : insmod, modprobe
    • modprobe는 dependenct를 갖고 있는 커널 모듈을 삽입해 줌
  • ex) insmod mymod count =10
    • count 
      • : mymod 안에 선언되어 있는 전역변수
      • 초기값 명시 -> compile 타임에 정해지지x, insmod할 때 정해진다. 
      • 컴파일 타임에 정해지는 것 보다 더 flexible

module_param() 

: 모듈 매개변수 정의 

static int count = 0;

module_param(count, int, S_IRUGO);
  • insmod할 때, 초기값을 명시해 줄 수 있는 것들을 선언해주는 것 
  • 함수 밖에서 선언한다. 
  • Arguments
    • Name of the variable (변수 이름) 
    • type (타입)
    • Permission (퍼미션)
      • S_IRUGO : insmod시 값을 설정할 수 있지만, 이후 커널 모듈 내부에서는 변경불가
        * 상수처럼 값을 바꾸지 않고 쓰는 것이 일반적이다.
      • S_IRUGO|S_IWUSR : 커널 모듈 내에서 변경 가능 

Kernel Dependency

커널 버전에 대한 의존성을 커널 모듈에서 어떻게 처리할 것인가

  • 커널 버전이 바뀌면서 자료구조/함수가 바뀌어서 우리의 device driver도 바뀌어야 되는 경우 있다. 
    (그렇지 않으면 최신 버전에서는 동작하지 않을 수도 있다.)
  • LINUX_VERSION_CODE
    • : 커널 버전을 양의 정수로 표시해둔 것 
  • KERNEL_VERSION(major, minor, release)
    • : 커널 버전 번호를 받아서 정수값으로 표시해주는 함수
profile

DEVELOP

@JUNGY00N