리눅스 Tips, 리눅스 C/C++ 프로그래밍, 모바일 클라우드 동향 및 테스트 등

2012년 11월 30일 금요일

리눅스 시스템 프로그래밍 : 03 입력과 출력(1)



1. 소개

 기계는 사용자의 입력을 받아서, 프로그래밍 되어 있는 대로 일을 하고 그 결과물을 출력한다. 믹서기는 사과를 입력 받고 버튼을 누르면, 프로그래밍 되어 있는 대로 모터를 돌려서 사과를 잘개 쪼개고 그 결과물로 사과쥬스를 출력한다. 컴퓨터는 정보를 처리하기 위한 기계로 입력을 받아서 처리하고 그 결과를 출력한다는 점에서 봤을 때, 근본적으로 믹서와 다를 바가 없다. 믹서와 다른 점이라면 입력으로 사과 대신 (비트로 이루어진)정보를 입력 받아서 처리하고 그 결과물로 정보를 출력한다는 점 정도일 것이다.

 여러분은 이미 컴퓨터 시스템은 키보드를 통해서 입력받은 데이터를 프로그램에 넘겨서 처리하고 그 결과물을 모니터로 출력하고 있다는 것을 알고 있을 것이다. 처리하고자 하는 데이터의 종류에 따라서 입력기기가 마우스나 펜, 터치 스크린이 되고, 출력기기 역시 파일, 프린터, 테이프 등이 될 것이다.

 이번 장에서는 컴퓨터 시스템에서의 입력과 출력을 제어하는 방법에 대해서 알아볼 것이다.

2. 모든 것은 파일이다

 유닉스에서는 모든 것을 파일로 취급한다. 하드디스크에 존재하는 파일, 디렉토리는 물론이고 네트워크 카드, 사운드 카드, 키보드, 마우스, 하드디스크 그 자체 까지 모두 파일로 취급한다. 유닉스 시스템을 처음 접할 때 꽤나 혼동되는 부분이기도 하다. 윈도우에는 파일은 단지 하드디스크 상에 존재하는 논리적인 정보의 집합을 그 대상으로 하기 때문이다. 예를 들자면 하드디스크는 C: D:와 같은 파일이 아닌 장치로 인식한다. 그러나 유닉스 시스템에서는 장치도 파일로 취급된다. 리눅스도 유닉스와 동일한 파일시스템을 가지고 있으므로, 리눅스를 예로 들어서 설명하겠다. 리눅스에서 하드디스크는 /dev/hda1, /dev/hda2 이런식으로 하드디스크상의 파일로 존재한다. 뿐만 아니다. 사운드 카드는 /dev/dsp, 프린트는 /dev/lp, cdrom은 /dev/cdrom 의 이름을 가진 파일로 존재한다.

 일반 사용자의 입장에서 장치를 파일로 인식하는 것이 불합리해 보일 수 있다. 그러나 개발자 입장에서는 매우 합리적인 방법이다. 모든 장치라는 것은 입력을 받아들여서 출력하는 매커니즘을 가지는데, 이는 파일의 매커니즘과 완전히 동일하기 때문으로 파일을 다루는 것과 동일한 방식으로 다른 장치들도 접근할 수 있음을 의미한다. 사운드 카드를 예로 든다면, test.wav 파일을 읽어서 /dev/dsp에 쓴다는 식으로 사운드를 플레이 할 수 있다. 실제로 프로그래밍 할 때도 일반 파일을 읽고 쓰는 것처럼 장치들에 접근할 수 있다. 물론 일반 파일들에 읽고 쓰는 것 보다는 약간 복잡하긴 하지만 원리적으로는 동일하다.

3. 파일의 종류

 위에서 예상했겠지만 파일이라고 해서 다 같은 파일은 아니다. 일반적으로 알고 있는 비트 데이터를 저장한 파일이 있는가 하면, 장치와 대응되는 파일도 있다. 내부 통신과 외부 통신을 위한 소켓파일(리눅스는 네트워크 통신도 파일을 통해서 한다), 파이프와 대응되는 파일, 디렉토리와 대응되는 파일 등이 있다. 예컨데 모든 것이 파일이다.

 리눅스에서는 ls 명령을 이용해서 이러한 파일의 종류를 알아낼 수 있다.

# ls -al
drwxr-xr-x 12 root root       13820 2007-11-12 22:19 .
drwxr-xr-x 21 root root        4096 2007-10-31 23:47 ..
drwxr-xr-x  2 root root         100 2007-11-13 06:45 .initramfs
-rw-r--r--  1 root root           0 2007-11-13 06:45 .initramfs-tools
drwxr-xr-x  3 root root          60 2007-11-13 06:45 .static
drwxr-xr-x  5 root root         120 2007-11-12 22:19 .udev
lrwxrwxrwx  1 root root          13 2007-11-13 06:45 MAKEDEV -> /sbin/MAKEDEV
crw-rw----  1 root root     10,  63 2007-11-12 21:46 acpi
crw-rw----  1 root audio    14,  12 2007-11-12 21:46 adsp
crw-rw----  1 root audio    14,   4 2007-11-12 21:46 audio
drwxr-xr-x  3 root root          60 2007-11-13 06:45 bus
lrwxrwxrwx  1 root root           3 2007-11-13 06:45 cdrom -> hda


 ls 의 가장 앞 필드의 첫 문자가 파일의 종류를 나타낸다. 아래는 ls 를 통해서 알아낼 수 있는 파일의 종류이다. 파일들은 아래의 종류 중 하나에 포함된다. pipe, 링크, 소켓 등에 대해서는 나중에 자세히 언급할 것이다. 우선은 아래와 같은 다양한 종류의 파일이 있다는 것만 이해하고 넘어가도록 하자.

-
일반파일
txt, jpg, wav, pdf 등 ... 
d
디렉토리
디렉토리
l
링크
심볼릭 링크 또는 하드 링크
c
장치
프린터, 사운드 카드, CD ROM 등의 장치
s
소켓
프로세스 간 통신에 사용
p
파이프
파이프

4. 파일 열기

 파일을 다루는 기본적인 흐름은 다음과 같다.

  1. 파일을 연다(open).
  2. 데이터를 읽거나(read), 데이터를 쓴다(write).
  3. 파일을 닫는다(close).

 가장 먼저 해야할 일이 파일을 open하는 것임을 알 수 있다. 이것은 커널에게 파일을 가지고 작업할 수 있도록 요청하는 것으로, 커널은 여러 가지 조건을 판단해서 파일을 open 해줄 것인지 아닌지를 결정하고, 그 결과를 open을 요청한 프로세스에게 리턴한다.

 파일을 오픈해 줄것인지 아닌지를 결정하는 데에는 다음과 같은 이유가 있다.
  1. 실제로 존재하는 파일인지 여부 확인
  2. 존재하지 않는 파일인 경우 새로 파일을 생성할 것인지 여부 결정
  3. 파일을 쓸 수 있는 권한이 있는지 확인 :
    리눅스는 다중 사용자 운영체제로 파일을 비롯한 모든 자원에 대한 접근 권한이 설정되어 있다. 따라서 해당 파일을 사용할 수 있는 권한이 있는지 확인해야 한다.



  4.1 open 시스템콜을 이용한 파일 열기

 파일 작업을 하기로 마음을 먹었다면, 커널에 정해진 파일을 열 수 있도록 허용해 달라고 요청을 해야 할 것이다. 커널에 요청을 할 수 있도록 지원되는 함수를 시스템 콜(혹은 시스템 함수)라고 언급했던 것을 기억하고 있을 것이다. 리눅스는 파일 오픈과 관련된 요청을 위해서 open 이라는 시스템 함수를 제공한다.

 open함수는 다음과 같이 선언되어 있다.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)

pathname : 열기를 요청하는 파일이다. 상대 경로 혹은 절대 경로를 지정할 수 있다.
flags : 어떤 방식으로 열 것인지를 결정하기 위해서 사용하며, bitwise연산을 이용해서 다양한 방식으로 조합할 수 있다. 다음은 대표적으로 사용되는 flag 들이다.
  • O_RDONLY
    읽기 전용으로 파일을 연다. 쓸 수 없다.
  • O_WRONLY
    쓰기 전용으로 파일을 연다.
  • O_RDWR
    읽기와 쓰기 모두 가능하도록 파일을 연다.
  • O_CREAT
    파일이 존재하지 않을 경우 파일을 생성한다.
  • O_EXCL
    O_CREAT를 써서 파일을 오픈 할 경우, 이미 파일이 존재한다면 error를 리턴하게 한다. 파일을 덮어쓰거나 하는 실수를 방지하기 위한 용도로 사용할 수 있다.

mode : 파일의 권한을 결정하기 위해서 사용하며, 생략이 가능하다. 파일이 생성되면 파일에 대한 소유자와 그룹은 자신이 된다. 이 인자를 사용하면 owner(사용자), group(그룹), other(타인) 각각에 대해서 읽기, 쓰기, 실행 권한을 부여할 수 있다. 역시 bitwise 연산을 이용해서 다양한 조합이 가능하다.
  • S_IRWXU
    00700 모드로 파일 소유자에게 읽기, 쓰기, 쓰기 실행권한을 준다.
  • S_IRUSR
    00400 으로 사용자에게 읽기 권한을 준다.
  • S_IWUSR
    00200 으로 사용자에게 쓰기 권한을 준다.
  • S_IXUSR
    00100 으로 사용자에게 실행 권한을 준다.
  • S_IRWXG
    00070 으로 그룹에게 읽기, 쓰기, 실행 권한을 준다.
  • S_IRGRP
    00040 으로 그룹에게 읽기권한을 준다.
  • S_IWGRP
    00020 으로 그룹에게 쓰기권한을 준다.
  • S_IXGRP
    00010 으로 그룹에게 실행권한을 준다.
  • S_IRWXO
    00007 으로 기타 사용자 에게 읽기, 쓰기, 실행 권한을 준다.
  • S_IROTH
    00004 으로 기타 사용자 에게 읽기 권한을 준다.
  • S_IWOTH
    00002 으로 기타 사용자 에게 쓰기 권한을 준다.
  • S_IXOTH
    00001 으로 기타 사용자 에게 실행 권한을 준다.

 예를 들자면 다음과 같은 방식으로 파일을 열 수 있을 것이다.

// 파일이름 hello.txt 에 데이터를 (단지)쓰기 위해서 연다.
// 파일이 없을 경우 생성하며
// 권한은 640 으로 한다.
open("hello.txt", O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);

 실제로 위의 권한으로 파일을 오픈하는 프로그램을 만들어 보도록 하자. 아래의 프로그램은 단지 파일을 열기만 할 뿐이지만 성공적으로 파일을 생성할 것이다. 프로그램의 이름은 hello.c 로 하겠다. 프로그램의 작성과 실행은 yundram계정을 이용했다.

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  int fd;
  fd = open("hello.txt", O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
}

 ls(1)를 이용해서 hello.txt를 확인해 보도록하자.

yundream@yundream:~$ ls -al hello.txt
-rw-r----- 1 yundream yundream 0 2007-11-20 20:23 hello.txt

 소유자과 그룹이 yundream이고 640의 권한을 가지는 파일이 생성되었음을 알 수 있다. 파일을 열기만 했을 뿐, 아무런 작업을 하지 않았기 때문의 파일의 크기는 0이다.

  4.2 file descriptor

 open 함수를 다시 보도록 하자. open 함수는 리턴 결과로 다루게 될 파일의 이름이 아닌 int형 정수를 넘겨주는 것을 알 수 있다. 이 int형 정수가 바로 파일을 가리키는 역할을 한다. 파일을 지정하기 때문에 'file descriptor' 혹은 '파일 지정번호' 라고 한다. 이것은 우리가 일반적으로 알고 있는 숫자가 아닌 열려진 파일객체를 가리키는 것임에 유의하기 바란다.

 open 을 이용해서 파일을 성공적으로 열었다면, 이후의 모든 쓰기/읽기 등의 작업은 파일이름 대신 파일지정번호를 이용하게 된다.

    +------+
    | FILE |<---- file discriptor = open
    |      |
    +------+
    open으로 리턴된 int형 정수는 file discriptor 로써, 열린 파일을 가리킨다.

 파일 지정번호는 0 이상이여야 한다. 0 보다 작은 경우는 어떤 이유로 파일을 여는 것이 실패했음을 의미한다. 위의 프로그램은 아래처럼 파일 오류까지 검사하는 좀 더 그럴듯한 프로그램으로 바꿀 수 있을 것이다.

  fd = open("hello.txt", O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
  if (fd < 0)
  {
    perror("file open error:");
    return 1;
  }

다음 포스트에 이어서...

댓글 없음:

댓글 쓰기