저수준 파일 입출력
-파일을 융통성있고 빠르게 처리하려면 버퍼와 카운터, 파일포인터를 직접서술하는 저수준언어의 입출력 함수를 취급해야한다.
저수준 화일 입출력 함수들
fd = open("화일 이름 , 액세스 방식\[, 모드\]);
/\* 화일이 있으면 그 내용을 지우고 없으면 생성, fopen의 "w"와 같음 \*/
fd = open("data.dat", O_WRONLY \| O_CREAT \| O_APPEND, 모드);
/\* 화일이 있으면 그 끝으로 이동하고 없으면 생성, fopen의 "a"와 같음 \*/
fprintf(stderr,"Error: Cannot open data.dat\n");
:
저수준의 화일 입출력에서는 FILE이란 구조 대신 간단하게 각 화일마다 번호를 사용하는데, 이를 화일 식별자(file descriptor), 또는 핸들(handle)이라고 한다. 이 핸들은 0 이상의 값을 가지고 있는데 실제로 0과 1, 2 는 고정된 의미(핸들 0은 표준 입력을 위한 번호이며 1은 표준 출력, 그리고 2는 표준 에러로 사용)를 갖고 있어서 화일을 처음 열게 되면 그 화일의 핸들은 3이 된다.
윈도우즈 상에서는 저수준 입출력 함수에 모두 '_'를 붙인다. 예) _open, _write, _O_RDONLY, _close ... |
open 함수
저수준의 화일 입출력에서도 화일을 사용하기 위해서는 화일을 먼저 열어야 하며, 이때 다음과 같이 open 함수를 사용한다.
int fd;
위에서 [CLang: ]는 역시 생략할 수 있는 부분을 의미하며, 화일 이름은 fopen과 같이 열 화일의 이름이고 액세스 방식은 이 화일을 어떻게 열 것인가인데, fopen과는 달리 다음과 같은 형태로 사용한다.
모드 | 값 | 내 용 |
O_RDONLY | 0x0001 | 읽기 전용으로 화일을 연다. |
O_WRONLY | 0x0002 | 쓰기 전용으로 화일을 연다. |
O_RDWR | 0x0004 | 읽고 쓰기 위해 화일을 연다. |
O_CREAT | 0x0100 | 화일이 없을 경우 새로운 화일을 만든다. |
O_TRUNC | 0x0200 | 현재 있는 화일의 내용을 0으로(제거) 한다. |
O_EXCL | 0x0400 | O_CREAT과 함께 사용하며, 화일이 없을 경우에만 연다. |
O_APPEND | 0x0800 | 화일을 쓰기용으로 열고 화일 포인터를 화일의 끝에 위치시킨다. |
O_TEXT | 0x4000 | 화일을 텍스트 형식으로 연다. |
O_BINARY | 0x8000 | 화일을 이진 형식으로 연다. |
위의 O_로 시작하는 것들은 모두 상수로 이의 정의는 fcntl.h(이것은 file control의 약자)에 들어있기 때문에 open 문을 사용하려면 반드시 fcntl.h를 포함하여야 한다.
위의 것들은 액세스 방식의 한 조건들로 여러 개를 동시에 같이 사용할 수 있으며 이 때에는 각 조건들을 '|'(비트 연산자 OR)를 이용해서 묶으면 된다.
data.dat 란 화일을 읽기 전용으로 열고자 할 때에는 다음과 같이 하면 된다.
fd = open("data.dat", O_RDONLY);
그리고 기존의 화일이 있으며, 이를 지우고 쓰기 전용으로 열 때에는 다음과 같이 하면 된다.
fd = open("data.dat", O_WRONLY \| O_TRUNC);
O_TRUNC이 추가 되었는데, 만약 이를 써 주지 않으면 쓰기 전용이라도 화일의 내용은 없어지지 않는다. 반면에 화일의 끝에 추가하고자 할 때에는 다음과 같이 O_APPEND를 써 주면 된다.
fd = open("data.dat", O_WRONLY \| O_APPEND);
fopen 문과 다른 것은 해당 화일이 없는 경우 화일이 만들어지지 않는다는 것이다. 만약 화일이 없을 때 화일을 만들도록 하고 싶으면 다음과 같이 O_CREAT를 지정하여야 하며, 이 때에는 세번재 인자가 필요하다.
fd = open("data.dat", O_WRONLY \| O_CREAT \| O_TRUNC, 모드);
세번째 인자인 모드에는 액세스 방식에서 O_CREAT 플래그가 지정된 경우, sys\stat.h(TC의 경우)에 정의되어 있는 다음 기호 중 하나가 사용된다(일반적으로 8진수 0700을 주면 된다).
S_IWRITE /* 써 넣기 가능 */ S_IREAD /* 읽어 내기 가능 */ S_IREAD | S_IWRITE /* 읽기 / 쓰기 가능 */ |
읽기용 화일에서는 모드를 생략해도 화일의 사용에는 별 지장이 없다.
그밖에 O_RDWR은 읽고 쓰고자 할 때 사용하며 O_EXCL은 O_CREAT하고만 같이 사용하는데, 지정한 화일이 있으면 에러가 난다. 즉 화일이 없는 상태에서 새로 만들고자 할 때에는 O_EXCL 과 O_CREAT를 같이 사용하면 된다.
open이 제대로 화일을 열게 되면 음이 아닌 정수값을 반환하는데, 그 화일의 핸들을 계산 값으로 산출하게 되며 에러 발생의 경우 -1을 계산 결과로 산출하게 된다. 따라서 다음과 같이 항상 조사하는 것이 필요하다.
if ((fd = open("data.dat", O_RDONLY)) == \-1) {
각 장치에 대응하는 디바이스 파일은 "/dev"디렉토리에서 관리된다. 디바이스 파일은 일반 파일과는 다르게 create()함수에 의해 생성되지 않고mknod 유틸리티나 mknod()함수를 통해 생성된다.
1.mknod [디바이스 파일명] [디바이스 타입] [주 번호] [부 번호]
2.[root@ ]# mknod /dev/devfile c 240 1
디바이스 파일에서 다루는 내용은 다음과 같다.
- 디바이스 타입 정보 - 디바이스가 문자형인지 블록형인지 구분한다.
- 주 번호, 부 번호 - 응용 프로그램과 디바이스 드라이버를 연결하는 고리. 주 번호로 디바이스 드라이버를 찾고 부 번호로 실질적인 디바이스를 찾는다.
디바이스 파일과 일반 파일의 가장 큰 차이점으로는 일반 파일은 데이터를 쓰면 보존되고, 쓴 만큼 크기가 증가하는 반면 디바이스 파일은 데이터를 써도 정보가 보존되지 않는 대신 하드웨어로 데이터를 전달하고 하드웨어에서 발생한 데이터를 읽어올 수 있다.
파일 입출력 함수
C 라이브러리에는 두가지 종류의 파일 입출력 함수가 있다. open(), read(), write(), close()의 저수준 파일 입출력 함수와 fopen(), fread(), fwrite(), fclose()의 스트림 파일 입출력 함수이다. 저수준 파일 입출력 함수는 시스템에서 제공하는 파일 함수이고 여기에 중간 버퍼 개념을 도입하여 스트림 처리가 가능하게 만든 것이 스트림 파일 입출력 함수이다. 스트림 파일 입출력 함수는 입출력을 효율적으로 처리하기 위해 중간 처리용 버퍼가 있으며 fprintf(), fscanf()와 같은 함수를 이용해 형식화된 형태로 읽고 쓸 수 있다.
디바이스 파일에서는 스트림 파일 입출력이 아닌 저수준 파일 입출력 함수를 사용한다.
디바이스 드라이버를 구동하기 위한 디바이스 파일에는 다음과 같은 몇 가지의 저수준 파일 입출력 함수만이 쓰인다.
저수준 파일 입출력 함수 | 기능 |
open() | 파일이나 장치를 연다. |
close() | 열린 파일을 닫는다. |
read() | 파일에서 데이터를 읽어온다. |
write() | 파일에 데이터를 쓴다. |
lseek() | 파일의 쓰기나 읽기 위치를 변경한다. |
ioctl() | read(), write()로 다루지 않는 특수한 제어를 한다. |
fsync() | 파일에 쓴 데이터와 실제 하드웨어의 동기를 맞춘다. |
int fd = -1
fd = open("/dev/mem", O_RDWR | O_NONBLOCK);
if(fd < 0)
{
//에러처리
}
...
close(fd);
디바이스 파일은 일반 파일과 달리 저수준 파일 함수에서 파일 생성을 처리하는 옵션이나 함수를 사용하지 못하므로 O_CREAT와 관련된 옵션을 사용하지 못한다.
디바이스 파일 열기에서는 다음과 같은 경우에 에러가 발생한다.
디바이스 파일이 없다.
디바이스 파일에 연결된 디바이스 드라이버가 없다.
디바이스 드라이버에서 에러 조건이 발생한다.
또한 디바이스 입출력 처리에서 프로세스가 블록되어서는 안되기 때문에 O_NONBLOCK 이나 O_NDELAY 옵션을 사용하여 입출력이 완료되지 않더라도 저수준 파일 입출력 함수가 즉시 종료되도록 해야 한다.
파일 읽고 쓰기는 일반 파일 읽고 쓰기와 동일하게 사용한다. 디바이스 파일을 다룰 때 일반 파일과 다른 점은 다음 두 가지다.
블록 모드에 대한 처리 여부
요구된 데이터의 수와 처리된 데이터 수의 차이 -> 요구된 것과 다를 때 처리를 해주어야 함.
르
또한 한가지 가장 중요한 차이점은 디바이스 파일의 데이터를 읽거나 쓸 때, 일반 파일과는 다르게 데이터가 보존되지 않는 점이다.
디바이스 파일에서는 파일 포인터의 의미가 일반 파일과는 다르며 디바이스 드라이버 마다 그 정의가 다르다. 또한 대부분의 디바이스 파일에는 사용되지 않으며 메모리나 하드디스크와 같은 기억 장치와 관련된 경우에만 처리된다.
off_t ret_pos;
...
ret_pos = lseek(fd, 1234, SEEK_CUR);
위치 변위의 값은 바이트 단위이고 디바이스 파일에서는 파일의 처음과 끝이라는 개념이 없기 때문에 SEEK_CUR을 사용하여 현재 위치를 기준으로 파일의 위치를 지정한다.
연결된 하드웨어에 따라 디바이스 파일을 다르게 제어해야 하나 모든 제어를 read(), write() 만으로 처리하기 어려우므로 ioctl()함수를 사용한다.
디파이스 파일에 적용된 변경사항은 디바이스 드라이버의 구현 방식에 따라 실제 하드웨어에 바로 저장될 수도 있고, 디바이스 드라이버 내부의 버퍼에 저장될 수도 있다. 디바이스 드라이버 내부 버퍼에 있는 데이터를 모두 적용해야 할 때 fsync() 함수를 사용한다.
int ret;
...
ret = fsync(fd);
주의할 점은 디바이스 파일에 따라 실행 시간이 길어지거나 함수가 종료되지 않을 수도 있다는 것이다. 따라서 알람과 같은 처리를 이용하여 한계 시간을 지정하는 등의 처리가 필요하다.
- END OF FILE
'Linux 커널' 카테고리의 다른 글
makefile분석 (0) | 2010.09.28 |
---|---|
source insight 프로젝트 생성하기 (0) | 2010.09.28 |
u-boot (0) | 2010.09.28 |
리눅스 명령어 (0) | 2010.09.07 |
리눅스 vi 사용법 (0) | 2010.09.07 |