리눅스 디바이스 드라이버 Linux device driver / 읽기 read() / 쓰기 write() / 열기 open() / 닫기 close() - 1

조영규의 블로그

2014. 5. 29. 02:58 from Engineering


인트로

디바이스 드라이버에 관해 공부 중에 자료를 찾다보니 제대로 된 자료를 찾을 수 없었다. 쉬운 말로 이해되게 정리한 자료가 있었으면 했는데.. 그래서 직접 찾은 자료와 공부한 내용을 알기 쉽게 정리했다. 더 쉬운 설명에 대한 이야기나 잘못된 내용이 있다면 메일로 알려주거나 댓글을 달아 알려주길 바란다.


리눅스에서의 디바이스

  • 리눅스에서 디바이스는 특별한 하나의 파일처럼 취급된다. 따라서 파일처럼 access가 가능하다.
  • /dev 에서 실제 파일처럼 확인이 가능하다.
  • 각 디바이스들은 major number와 minor number를 가지고 있다.
  • 같은 디바이스들은 모두 같은 major number를 가진다. 적합한 디바이스 드라이버를 연결 하기위해 사용한다. 
  • 이렇게 뭉뚱그려 하나의 번호로 구분시 각각의 디바이스가 개별적으로 구분이 불가능하므로, minor number를 사용해 각각을 구분한다.

디바이스 드라이버
  • 디바이스 드라이버란 디바이스를 구동시키는 프로그램을 의미한다.
  • 그 종류에는 문자(character) 디바이스 드라이버, 블록(block) 디바이스 드라이버, 네트워크(network) 디바이스 드라이버가 있다.


각각을 하나 하나 살펴보자.

  • 문자 디바이스 드라이버는 buffer cache를 사용하지 않으며 raw data를 제공한다. 예를 들어 Terminal, keyboard, sound card, scanner, printer 드라이버등이 있다.
  • 블록 디바이스 드라이버는 random access가 가능하며 블록 단위로 입출력이 가능하다. 또한 file system에 의해 마운트되어 관리, 사용된다. 예를 들어 hard disk, RAM, CD-ROM등의 디바이스가 속한다.
  • 네트워크 디바이스의 경우 위의 드라이버들이 사용하는 system call대신에 socket(), bind()등의 특별한 system call을 사용한다. 네트워크 다비이스는 통신을 통해 패킷을 송수신 할 수 있는 장치를 이야기하는데 Ethernet, ATM 등의 드라이버가 여기에 속한다.


리눅스 디바이스 드라이버의 구조

간단하게 그림으로 나타내면 다음과 같다. 애플리케이션이 kernel이 제공하는 system call interface를 통해 Device driver를 제어하고 그 아래 하드웨어 계층의 Device를 제어한다. 실제로 애플리케이션이 사용하는 것은 system call interface다. 아래에 나올 함수들만 부르면 된다.




리눅스에서 디바이스 드라이버를 구현한다는 것.

  • 각 디바이스들은 일반적인 파일과 유사한 인터페이스를 이용하여 관리된다.
  • 각 디바이스들은 실제로 파일형태로 존재하고 커널은 파일 operation을 이용하여 I/O를 수행하도록 인터페이스가 구성되어져 있다.
  • 디바이스 드라이버를 구현한다는 것은 파일 operation 구조체에서 요구되는 기능들을 프로그래밍 한다는 것을 의미한다.
  • 파일 operation 구조체는 파일 시스템을 통해 디바이스 드라이버와 프로그램을 연결 시켜주는 구조체이다.


system call interface. (애플리케이션에서의 드라이버 실제 호출 함수)

다음과 같은 순서로 디바이스 드라이버를 이용하는 코드를 작성한다.

여기서 나오는 함수들은 파일에서도 똑같이 사용한다. 왜냐하면 리눅스에서 디바이스들은 일반적인 파일과 유사한 인터페이스를 사용하기 때문이다.

  • 1. open()을 이용하여 device driver file을 연다.
  • 2. write()를 이용하여 디바이스에 무엇인가 적어 디바이스를 제어한다.
  • 3. read()를 이용하여 디바이스에서 무엇인가 정보를 얻어온다.
  • 4. close()를 이용하여 device driver file을 닫는다.
당연하게도, 2번과 3번은 경우에 따라 둘 중 하나 혹은 둘 다 생략이 가능하다.


이때, open() ?

파일 혹은 디바이스를 열기위해 사용한다. 열 때 다양한 옵션을 줄 수 있다. 원래 C언어에서 파일을 열 때 fopen()이라는 표준함수를 제공한다. 이것은 C언어가 제공해주기에 조금 더 사용하기 쉽게 만들어져 있으며, open()에 비하여 portable하다. open()의 경우 리눅스의 system call이기 때문이다. fopen()도 내부적으로 결국은 open()을 호출하지만, 한겹 더 래핑되어 있어 타 운영체제에서도 사용할 수 있다.


  • 헤더 : #include <fcntl.h>


  • 형태 : int open (const char *FILENAME, intFLAGS) / int open (const char *FILENAME, intFLAGS[, mode_t MODE])


  • 옵션 :


반드시 처음에 써주어야 하는 옵션

 O_RDONLY

 ReaD only. (읽기만 하겠다.)

 O_WRONLY

 WRite only. (쓰기만 하겠다.)

 O_RDWR

ReaD와 WRite를 하겠다. (읽기도 하고 쓰기도 하겠다.)

 기타 추가 가능한 옵션 (반드시 처음에 써주어야 하는 옵션 | ##여기에 들어갈 옵션## ) - '|'기호다.

 O_CREAT

 해당 파일이 없을시 생성한다. 이 경우 생성한 파일이다 보니 접근 권한을 명시해 주어야 한다. 만약 파일이 현재 존재한다면 파일을 덮어쓴다. (참고로 오타가 아니라 실제로 CREAT까지다. E 없다.)

예 : open( "jwmx", O_WRONLY | OCREAT, 0644);

 O_EXCL

 O_CREAT때 함께 사용하면 파일이 존재할 시 -1을 반환하여 open 되지 않도록 막는다.

 O_TRUNC

 기존의 파일 내용을 모두 삭제한다.

 O_APPEND

 파일을 첨부모드로 연다. 파일 쓰기 포인터(마커)가 파일 끝에 위치한다.

 O_NOCITTY

 파일을 열 process에 대해 device를 제어 터미널에 할당하지 말라는 옵션. device가 현재의 terminal을 제어하지 못하도록 한다. 이는 통신시에 <CTRL>+C 문자가 와서 프로그램이 종료되지 않을 수 있도록 해준다. (이 말이 무슨 말인지 몰라도 된다. 이 글을 읽는 대부분의 독자에게는 필요없는 옵션이다.)

 O_NONBLOCK

 읽을 내용이 없을 때 기다리지 않고 바로 리턴한다.

 O_SYNC

 쓰기시 쓰기가 실제로 끝날 때까지 기다린다. 쓰기가 완료되어야 리턴한다.



  • 인수


 char *FILENAME

 파일 이름

 int FLAGS

 옵션이 여기에 들어간다.

 [, mode_t MODE]

 O_CREAT 때 파일 접근 권한 표시


  • 반환값 : int (열기 성공시 양수 반환 == 파일 디스크립터 반환, 실패시 -1 반환)


  • 참고 : 파일 디스크립터란 파일 혹은 디바이스, 소켓등을 운영체제가 관리하기위해 필요로하는 정보를 가지고 있는 FCB(file control block)에 부여된 숫자를 가리킨다. 기본적으로 양의 정수로 부여된다. 일련번호 혹은 아이디라 생각하면 된다. 운영체제가 관리를 위해 정보를 가지고 있는 블록에 부여된 일련번호 혹은 아이디.

http://taesun1114.tistory.com/12 참조


  • 사용 예 :


int fd = open( "test1.txt", O_WRONLY | O_CREAT | O_EXCL, 0644);

혹은

int fd = open( "test2.txt", O_RDWR );



이떄, write() ?

open() 함수로 열기를 한 디바이스 혹은 파일에 쓰기 작업을 한다. 


  • 헤더 : #include <unistd.h>

  • 형태ssize_t write (int fd, const void *buf, size_t n)

  • 인수

 int fd

 디스크립터. 번호. 오픈시에 받은.

 void *buf

 파일에 쓸 내용을 담은 버퍼

 size_t n

 쓰기할 바이트 수


  • 반환값 : ssize_t (정상적 쓰기를 했다면 쓰기를 한 바이트 개수를, 실패했다면 -1을 반환)

  • 사용 예 :


 char  *temp = "forum, falinux.comn";

...생략

write( fd, temp, strlen( temp) );



중요 : 황당하게도 open()함수는 fcntl.h 에 정의 되어 있지만 write(), read(), close()는 unistd.h에 정의 되어 있다.



이때, read() ?

open 디바이스 혹은 파일을 읽는다.


  • 헤더 : #include <unistd.h>

  • 형태ssize_t read (int fd, void *buf, size_t nbytes)

  • 인수

 int fd

 디스크립터. 번호. 오픈시에 받은.

 void *buf

 파일에서 읽은 내용을 담은 버퍼

 size_t nbytes

 읽기 할 바이트 수 (일반적으로는 버퍼의 크기랑 동일)


  • 반환값 : ssize_t (정상적으로 실행되었다면 읽어들인 바이트 수를, 실패했다면 -1을 반환)


  • 사용 예 :


#define  BUFF_SIZE   1024 

...생략

char   buff[BUFF_SIZE];

...생략

read( fd, buff, BUFF_SIZE);



이때, close() ?

open된 디바이스던 파일이던 사용을 중지한다.


  • 헤더 : #include <unistd.h>


  • 형태int close(int fd)


  • 인수 : int fd - 파일 디스크립터 (open때 받은 번호를 의미한다. )


  • 반환값int (정상적으로 close 했다면 0을, 실패했다면 -1을 반환)


  • 사용 예


 close( fd );



- FALINUX 포럼 참조


comments powered by Disqus
태그, 트랙백, 검색 상자 토글
    1     >>