File4developers

깊이있는 삽질 Ubuntu Korea Community Wiki
이동: 둘러보기, 검색
  • 여기서는 리눅스의 파일과 디렉토리를 살펴보고, 그 파일과 디렉토리를 방법에 대해 알아보겠다. 파일 만들기, 열기, 읽기, 쓰기, 그리고 닫기에 대해 알려줄것이며, 예제를 통해 디렉토리를 생성하고 찾고 지우는 일련의 조작방법도 알아볼거다.근데 나도 잘 몰라
  • 리눅스 파일 IO 다루는 방법 알아보기 전에 파일, 디렉토리, 장치에 관련된 개념을 좀 알아볼거다. 파일과 디렉토리를 조작하기 Win32API같은 System Call을 사용할 필요가 있는데, 라이브러리 함수가 있다귀찮으신 분들이 다 만들어 두셨다 만세

목차

Linux / Unix에서 파일의 구조[편집]

  • "왜 파일구조를 알아야 함? 나 윈도우에서도 파일 존나 열고 닫아봤어 다 안다고"일단 명치 존나 쎄게 한대만 맞고 시작하면 안될까? 일단 좀 다르다.이런 씨댕 다 알면 그냥 안 읽으면 되잖아
  • Linux에서는 일반적으로 어플리케이션들은 디스크 파일, 시리얼 포트, 프린터, 기타 디바이스까지 파일로 쓴다. 여기서 예외적인게 몇가지가 있긴 한데 일단 신경쓰지말고.. 가장 중요한게 열기, 닫기, 읽기, 쓰기, ioctl이다.
  • 디렉토리 역시 파일의 한 종류고, 존나 옛날 SCO Unix 초기버전 같은거에서는 슈퍼유저도 디렉토리를 못 지웠다고 한다. 우린 그냥 지우면 된다.요즘 리눅스는 가끔 윈도우보다 편할때도 있다니깐 아 물론 가끔임

디렉토리[편집]

  • 일단 윈도우에서는 폴더라는 이름으로 부른다.
  • 파일은 그 이름과 관리정보, 그러니까 언제 만들어졌는지, 권한은 뭐가 필요한지 같은 정보를 가지고 있다. 관리정보는 inode에 저장되고, inode는 파일의 크기와 디스크상에 물리적으로 저장된 위치정보를 담고있다. 시스템은 파일의 inode 번호를 사용한다.
  • 디렉토리는 inode와 다른 파일들의 명칭을 저장하고 있는 파일이다. (디렉토리 파일 자체를 볼 수 있는 방법을 아는 사람은 추가바람)
  • 파일은 디렉토리에 정렬되며 보조 디렉토리를 포함할 수도 있다. 이러한 형태는 CP/M 시절부터도 써오던거다. drake라는 사용자는 보통 그의 홈디렉토리 /home/drake에 전자메일, 업무용 문서, 만든 프로그램 등을 저장하는 보조 디렉토리가 있을거다. 리눅스는 현재 사용자의 홈 디렉토리를 ~로 표시하는 존나 훌륭한 표기법을 가지고 있다. 귀차니즘의 승리 다른 사용자의 홈 디렉토리는 ~user로 표기된다. 예를들어 disint라는 사용자의 디렉토리는 ~disint이다. 일반적으로 각 사용자의 홈 디렉토리는 /home의 하위 디렉토리다.
  • /home 디렉토리는 /라는 루트 디렉토리의 서브 디렉토리다. /bin에는 시스템 프로그램이 저장돼 있고, /etc에는 시스템 환경설정파일이, /lib에는 시스템 라이브러리가 모여있다. 물리적인 장치를 나타내는 파일과 이러한 장치에 대한 인터페이스를 제공하는 것들은 /dev, /proc, /sys 에 있다.

파일과 장치[편집]

  • 하드웨어 장치도 유닉스에서는 파일로 표현된다.
  • 예를 들어 /mnt/cdrom으로 CDROM을 그라운딩마운트하려면 다음과 같다.GUI를 쓰면 그냥 인식된다. 즉 뻘짓임
$ mount -t iso9660 /dev/sdc /mnt/cdrom
$ cd /mnt/cdrom
  • 위의 내용은, 일단 부팅될때 cdrom을 찾으면 어디라고 나온다. 그게 /dev/sdc이었던거다. 그걸 /mnt/cdrom에다가 마운트한다는 의미다.

/dev/console[편집]

  • 이 장치는 시스템 콘솔이다. 에러메세지나 시스템 및 운영체제의 메세지는 이쪽으로 표시가 된다. 일반적으로 tty1~8, 시리얼포트쪽으로 뽑아낼 수 있다. 커널 설정에서 바꿔줄 수 있는데, 이 방법에 대해서는 다른 문서를 참조. 아니면 간결히 설명해주실 분이 추가바람

/dev/tty[편집]

  • 특별한 파일 /dev/tty는 닉네임이다. 현재 제어가 가능한 터미널을 지칭한다.
  • /dev/console과 다른점은, /dev/console은 고정되어 있고, /dev/tty는 바뀔 수 있다는 것.

/dev/null[편집]

  • 의미없는 장치.가끔 잉여로운 생활이 필요할때도 있지
  • 이 장치로 보내지는 모든 출력(output)은 무시된다.꼰대처럼
  • 이걸 읽으면 파일의 끝문자인 EOF가 반환되는데, 이는 cp명령을 사용할때 빈파일로 사용될 수 있다.
  • 일반적으로 디버그용의 문자를 출력하고 디버그가 종료됐을 때, 그냥 출력을 이쪽으로 돌리는 것을 많이 볼 수 있다.
$ echo do not want to see this > /dev/null
$ cp /dev/null empty_file
  • /dev안에 다른 장치들은 하드디스크나 플로피디스크, 통신 포트, 테이프 드라이브, CDROM, 사운드칩, 그리고 몇몇 시스템의 내부적 상태를 나타내는 장치들로 구성된다. 용량이 0인 파일을 생성하기 위해 null바이트의 원천으로 작동하는 /dev/zero라는 장치도 있다. 대부분은 루트권한이 필요하다. 일반 사용자가 하드디스크 저수준 입출력을 가능하게 한다면.. 헬게이트 오픈

시스템 호출과 장치 드라이버[편집]

  • 몇가지 함수로 파일과 장치를 컨트롤하거나 읽고 쓸 수 있다. 이런 함수를 시스템 콜이라고 부르며, 이것은 리눅스에 의해 직접 제공되고, 그 자체로 OS에 대한 인터페이스라고 볼 수 있다.
  • OS의 심장 커널은 많은 장치 드라이버를 갖고 있다. 이 드라이버는 시스템의 하드웨어를 제어할 수 있는 저수준 인터페이스 모음이다. 예를들어, 테이프 드라이브의 장치드라이버는 테이프 장치를 켜고 앞으로 감거나 뒤로 감거나 테이프에 직접 읽고 쓰는 방법이 있어야 테이프 장치를 쓸 수 있을거 아닌가. 또, 드라이버는 장치의 버퍼 등을 최대한 활용할 수 있게 만들어져 있다.
  • 하드디스크 드라이버 역시 사용자가 몰라도 되는, 섹터, 클러스터에 대한 접근을 할 수 있다. 랜덤억세스 역시 마찬가지. 변태가 아닌 일반 프로그래머가 '나 랜덤억세스로 할래'라고 하지 '랜덤억세스를 어떻게 하냐면..' 하고 프로그램을 짜진 않는다.
  • Read, Write가 아닌, 독특한 세팅은 ioctl() 함수를 이용해서 억세스하는게 일반적이다.
  • /dev/ 안의 디바이스파일도 그냥 일반 파일처럼 읽고 쓰는거다.
  • 장치 드라이버를 제어할 수 있는 저수준 함수에는 다음과 같은 것들이 있다.
  1. open - 파일 / 장치 열기
  2. read - 열린 파일 / 장치로부터 읽기
  3. write - 열린 파일 / 장치에 쓰기
  4. close - 파일 / 장치 닫기
  5. ioctl - 특별한 제어
  • ioctl이라는 시스템 호출은 필수적인 하드웨어 제어 기능을 제공한다. 시리얼포트같은 경우 read/write만 가능하면 어떻게 세팅을 하겠는가.존나 옛날에는 AT 어쩌구로 제어하긴 했었다 ATDT01410같이..
  • 시스템 콜이나 POSIX는 리눅스 맨페이지 2섹션에 있다.물론 영어 거기에 보면 시스템 콜에 대한 짤막한 설명이 있다.

라이브러리 함수[편집]

  • 입출력에 사용되는 저수준 시스템 콜을 사용할때 문제는 시스템 콜이 존내 비효율적이라는데 있다.
  1. 시스템콜을 사용하는것은 수행 측면에서 별로 안 좋다. 리눅스는 어플리케이션 코드를 실행하다가 모드 변경해서 커널 코드를 수행하고 다시 모드 변경해서 어플리케이션을 돌리는 방식이다. 시스템 콜은 그냥 함수 쓰는거에 비해 존나 느리다.
  2. 하드웨어는 버퍼가 있는데, 시스템 콜로 읽고 쓰면 버퍼를 안 거친다.물론 하드웨어에 대해 빠삭하게 알고 접근하면 이야기가 좀 다르긴 한데, 님 잘 알음?
  • 이에 비해 장치나 디스크에 효율적으로 입출력을 하기 위해 만들어진 표준 라이브러리가 있다. 라이브러리는 효율적으로 입출력을 하기 위해 만들어져 있으니, 시스템 콜을 최소화할 수 있다. 물론 표준 라이브러리 내부에서 시스템 콜을 쓰긴 하지만, 최대한 느려지지 않게 잘 만들어져 있다.
  • 라이브러리 함수는 리눅스 맨 페이지 3섹션에 있다. 보통 그와관련된 표준 include파일(헤더)를 갖는데, 표준 입출력 라이브러리에 대해서는 stdio.h같은 헤더파일이 있다.

저수준 파일 입출력[편집]

  • 각각 실행되는 프로그램을 프로세스라 부르는데, 이 프로세스는 많은 File Descriptors와 관련된다. 이건 열린 파일이나 장치들을 쓰기 위해 사용할 수 있는 작은 정수값이다. 이 값이 얼마나 많이 있는가는 유닉스 시스템이 어떻게 설계되어 있는가에 달려있다. 프로그램이 시작될 때, 보통 이미 열려져 있다면 이 세가지 중 하나의 값을 가진다.
  1. 0 - 표준 입력
  2. 1 - 표준 출력
  3. 2 - 표준 에러
  • open이라는 시스템 콜을 사용하면 파일이나 장치에 대해 다른 File Descriptor를 연결시킬 수 있다. 근데 자동으로 열려진 File Descriptor에 write를 사용하여 몇가지 단순한 프로그램을 생성할 수 있다.

Write[편집]

/* write */
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
  • write 시스템 콜은 filedes라는 File Descriptor와 관계된 파일에 쓰기 위해 buf로부터 첫 nbytes만큼의 바이트 수를 읽는다. 이건 진짜 write할 바이트의 수를 되돌려준다. File Descriptor에 에러가 있었다거나 write하려는 디바이스 드라이버가 블록 크기에 상관이 있다면 nbytes 이하가 될 수도 있다. 함수가 0을 반환하면, 파일의 끝(EOF)에 도달했다는 것이고, -1이라면 에러가 있었다는 것이며, 그 에러가 뭔 지 볼 수 있다.
/* simple_write.c */
#include <unistd.h>

int main()
{
  if ((write(1, "Here is some data\n", 18)) != 18)
    write(2, "A write error has occurred on file descripter 1\n", 47);
  return 0;
}
  • 이 프로그램은 단순히 표준 출력(콘솔)로 메세지를 낸다. 프로그램이 종료될 때 모든 열려있는 File Descriptor는 자동으로 닫히니까, 우린 파일을 닫아야 할 필요는 없지만, 안 쓰면 닫는게 좋다. 버퍼링된 출력을 할땐 걍 열어놓고 종료하면 망한다.
$ sudo ./simple_write
Here is some data
$

Read[편집]

/* read */
#include <unistd.h>

size_t read(int fildes, void *buf, size_t nbytes);
  • read 시스템콜은 File Descriptor랑 연결된 파일로부터 nbytes보다 큰 buf에 nbytes만큼의 크기로 읽어온다. 이 함수는 읽은 데이터의 바이트수를 반환하는데, 반환값이 nbytes보다 작을수도 있다. 만약 read 함수가 0을 반환하면, 읽을게 없다는 얘기다. 호출할때 에러가 발생하면 -1을 반환한다.
  • 이 프로그램은 simple_read.c라는 프로그램인데, 표준 입력에서 처음 128바이트를 읽어서 표준 출력으로 뿌려주는 프로그램이다. 입력이 128바이트 이하면 입력된 만큼만 뿌린다.
/* simple_read.c */
#include <unistd.h>

int main()
{
  char buffer[128];
  int nread;

  nread = read(0, buffer, 120);
  if (nread == -1)
    write(2, "A read error has occurred\n", 27);
  if ((write(1, buffer, nread)) != nread)
    write(2, "A write error has occurred\n", 28);
  return 0;
}
  • 프로그램을 실행하면 다음과 같은 결과를 볼 수 있다.
$ echo hello there | ./simple_read
hello there
$ ./simple_read < draft1.txt
draft1.txt$

open[편집]

  • 새로 File Descriptor를 만들려면 open이라는 시스템 콜을 사용해야 한다. creat라는 호출은 POSIX 표준이고, 리눅스에서 지원된다. 근데 요샌 잘 안 쓴다. 안 쓰는 이유는 앞에서 설명했음.
/* open */
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int open(const char *path, int oflags);
int open(const char *path, int oflags, mode_t mode);
  • open은 파일이나 장치에 경로를 만드는거다. 성공하면 read나 write에서 쓰게 될 File Descriptor를 반환한다. File Descriptor는 열때마다 생기는데, 프로그램 두개에서 파일 하나를 같이 쓸 수 있다. 근데 동시 입출력이 read면 그나마 괜찮은데, write를 두군데서 하면.. 더이상의 자세한 설명은 생략한다
  • path는 파일이름이고, oflags는 파일 모드다.
  • oflags는 다음 중 하나를 골라서 쓰면 된다.
  1. O_RDONLY - 읽기전용
  2. O_WRONLY - 쓰기전용
  3. O_RDWR - 읽고쓰기용
  • 일단 모드 선택을 하고 | 연산으로 추가 옵션을 줄 수 있다.
  1. O_APPEND - 파일이 있으면 지우지 않고, 뒤에다가 추가한다.
  2. O_TRUNC - 파일이 있으면 일단 지우고 시작한다.본격 조때바라 플레이
  3. O_CREAT - 파일이 없으면 만든다.
  4. O_EXCL - O_CREAT랑 같이 사용하여 파일을 만드는데, 이미 파일이 있다면 에러를 뱉는다.
  • 몇가지 옵션이 더 있는데 man 2 open 으로 확인할 수 있다.
  • open이 제대로 되면, File Descriptor를 양수(+)로 반환하고, 실패하면 -1을 반환한다. 실패시 errno라는 전역변수를 설정한다.
  • open에서 O_CREAT로 파일을 생성할 때 권한을 줄 수 있다. mode는 sys/stat.h에 정의되어 있다. | 연산자로 OR해서 쓴다.
  1. S_IRUSR - 소유자에게 읽기 권한
  2. S_IWUSR - 소유자에게 쓰기 권한
  3. S_IXUSR - 소유자에게 실행 권한
  4. S_IRGRP - 소유그룹에게 읽기 권한
  5. S_IWGRP - 소유그룹에게 쓰기 권한
  6. S_IXGRP - 소유그룹에게 실행 권한
  7. S_IROTH - 그외에게 읽기 권한
  8. S_IWOTH - 그외에게 쓰기 권한
  9. S_IXOTH - 그외에게 실행 권한
  • 예를들어
open("myfile", O_CREAT, S_IRUSR|S_IXOTH);

라고 해주었을 경우, myfile이란 이름의 파일을 만들면서 소유자에게 읽기 권한, 나머지는 실행만 할 수 있게 만들어진다.

$ ls -la myfile
0 -r-------x    1    drake    ubuntu    0 Jun 09 23:55 myfile*

umask[편집]

  • umask는 좀더 친숙한 숫자로 퍼미션을 정하는 함수다.
  • umask 명령어와 같은 방식으로 사용하면 된다. 즉, chmod로 777이면 rwxrwxrwx지만, umask는 000이 rwxrwxrwx이다.
  • 이 내용은 추후 추가

close[편집]

/* close */
#include <unistd.h>

int close(int fildes);
  • File Descriptor랑 파일 사이의 연결을 종료하기 위해 close를 사용할 수 있다. File Descriptor는 나중에 다시 쓸 수 있다. 성공은 0, 에러는 -1
  • 수행중인 프로그램 하나가 동시에 열 수 있는 파일의 수는 디바이스마다 다르지만, bits/stdio_lim.h내에 define된 FOPEN_MAX가 있다. 기본값은 16이다.

ioctl[편집]

/* ioctl */
#include <unistd.h>

int ioctl(int fildes, int cmd, ...);
  • ioctl은 디바이스 드라이버가 정의한대로의 방식으로 쓰인다.
  • 사용법은 각 디바이스 드라이버별로 다르므로 여기엔 안 적겠다.개물님들 도와주셈

파일 복사 프로그램[편집]

  • open, read, write를 가지고 copy_system.c라는걸 한번 짜보자.
/* copy_system.c */
/* 입력 파일은 존재하고 출력 파일은 존재하지 않는다고 가정한다 */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  char c;
  int in, out;

  in = open("file.in", O_RDONLY);
  out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  write(read(in, &c, 1) == 1)
    write(out, &c, 1);
  return 0;
}
/* POSIX 권고안에 따르면, unistd.h가 다른 헤더보다 우선해야 한다고 한다.*/
  • 프로그램을 실행하면 다음과 같은 결과가 나온다.

drake@debian:~/cc$ ls -l
total 15372
-rwxr-xr-x 1 drake drake     5176 Jun 10 09:54 copy_system
-rw-r--r-- 1 drake drake      375 Jun 10 09:54 copy_system.c
-rw-r--r-- 1 drake drake 15728640 Jun 10 09:54 file.in
drake@debian:~/cc$ time ./copy_system

real    2m44.143s
user    0m8.145s
sys     2m35.290s
drake@debian:~/cc$ ls -l
total 30732
-rwxr-xr-x 1 drake drake     5176 Jun 10 09:54 copy_system
-rw-r--r-- 1 drake drake      375 Jun 10 09:54 copy_system.c
-rw-r--r-- 1 drake drake 15728640 Jun 10 09:54 file.in
-rw------- 1 drake drake 15728640 Jun 10 09:58 file.out
drake@debian:~/cc$
  • 여기서 프로그램 실행시간 측정을 위해 time을 썼다.
  • 시스템콜의 느림을 보여준다. 150메가 복사하는데 2분 45초나 걸렸다. 여긴 안나왔지만 cpu도 존내 쓰고있었다.
  • 그래서 블럭단위로 한번 해보겠다.
/* copy_block.c */
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
  char block[1024];
  int in, out;
  int nread;

  in = open("file.in", O_RDONLY);
  out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
  while((nread = read(in, block, sizeof(block))) > 0)
    write(out, block, nread);
  return 0;
}
  • 실행 결과를 보자.
drake@debian:~/cc/wrox$ cc -o copy_block copy_block.c
drake@debian:~/cc/wrox$ ls -l
total 15384
-rwxr-xr-x 1 drake drake     5207 Jun 10 10:10 copy_block
-rw-r--r-- 1 drake drake      318 Jun 10 10:10 copy_block.c
-rwxr-xr-x 1 drake drake     5176 Jun 10 09:54 copy_system
-rw-r--r-- 1 drake drake      375 Jun 10 09:54 copy_system.c
-rw-r--r-- 1 drake drake 15728640 Jun 10 09:54 file.in
drake@debian:~/cc/wrox$ time ./copy_block

real    0m0.231s
user    0m0.012s
sys     0m0.220s
drake@debian:~/cc/wrox$
  • 예상시간보다 훨씬 빨라서 나도 놀랐지만캐쉬 만세 오오, 아무튼 중요한건 호출 횟수다.
  • 생각보다 시스템콜은 부하가 많이 걸린다. 이런걸 알고 있다면, 최적화에 도움이 된다.

파일 관리를 위한 다른 시스템콜[편집]

  • 이렇게 open, close, read, write, ioctl 말고도 다른 시스템 콜이 몇개 있다.

lseek[편집]

#include <unistd.h>
#include <sys/types.h>

off_t lseek(int fildes, off_t offset, int whence);
  • lseek이라는 시스템콜은 File Descriptor의 read/write 포인터를 세팅한다.
  • 파일 내에서 다음번에 읽거나 쓸 장소를 지정하는데 쓴다.
  • 파일내에 절대적인 위치나 상대적인 위치를 지정할 수 있다.
  • offset 인자는 위치를 구체화할때 쓰고, whence 인자는 어디를 기준으로 할건지 명시한다. whence는 다음중 하나로 사용한다.
  1. SEEK_SET - offset은 절대적인 위치다. 현재 위치가 몇이든 offset이 5면 다음 포인터는 5다.
  2. SEEK_CUR - offset은 현재 위치에서부터의 대상이다. 현재 위치가 5면 다음 포인터는 10이다.
  3. SEEK_END - offset은 EOF로부터의 대상이다. 파일 크기가 10이고 offset이 -2면 다음 포인터는 8이다.
  • lseek는 파일포인터가 설정된 파일 시작 위치로부터 측정된 offset을 바이트단위로 반환한다.
  • 실패하면 -1을 반환한다.
  • seek에서 offset으로 사용되는 off_t의 타입은 sys/types.h에서 정의되며, 구현 방식에 따라 다른 타입이다.

fstat, stat, lstat[편집]

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

int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
/* sys/type.h 포함 안해도 되긴 하는데, POSIX에서는 포함하는걸 권장하고 있다. */
  • 시스템콜 fstat는 open된 File Descriptor와 연결된 파일에 대한 상태 정보를 구조체로 반환한다.
  • 관련함수 stat랑 lstat는 명시된 파일에 대한 상태 정보를 반환한다. fstat랑 stat, lstat의 차이는 File Descriptor를 참조하느냐, 아니면 파일을 직접 읽어와서 보느냐 차이고, stat랑 lstat의 차이는 lstat은 symbolic link 파일이면 '링크다'라고 하지만 stat은 링크된 파일 정보를 읽어오는 차이가 있다.
  • 구조체 stat의 멤버의 구성은 /usr/include/sys/stat.h 혹은 /usr/include/i386-linux-gnu/sys/stat.h에 있다.저 중간에 끼는거 저거 더럽네

dup, dup2[편집]

#include <unistd.h>

int dup(int fildes);
int dup2(int fildes, int fildes2);
  • 시스템 콜 dup는 같은 파일을 액세스하는 두개 이상의 File Descriptor를 제공할 수 있다.
  • 다중 프로세스 환경에서 쓸 수밖에 없는 상황이 있다. 가능하면 나도 쓰는걸 말리고 싶다.

표준 입출력 라이브러리[편집]

  • 표준 입출력 라이브러리와 stdio.h 라는 헤더파일은 저수준에 비해 좀더 나은 방법이다. 뭐 버퍼같은것도 자유롭게 쓰고..
  • 저수준 라이브러리랑 거의 비슷하게 사용한다.
  • File Descriptor 대신 FILE * 구조체를 쓴다.

fopen[편집]

#include <stdio.h>

FILE *fopen(const char *filename, const char *mode);
  • fopen 라이브러리 함수는 대체로 open이랑 같다. 장치 제어하기에는 버퍼링때문에 좀 생각대로 움직여주지 않을때도 있다.
  • fopen은 filename에 해당하는 파일을 열고 FILE에 연결시킨다. mode 인자로 파일을 어떻게 열건지 결정한다.
  1. "r", "rb" - 읽기. 파일이 없으면 에러.
  2. "w", "wb" - 쓰기. 파일이 있으면 날려버린다.
  3. "a", "ab" - 쓰기. 파일이 있으면 뒤에 추가한다.
  4. "r+", "rb+", 'r+b' - 읽고쓰기.
  5. "w+", "wb+", 'w+b' - 읽고쓰기. 파일이 있으면 날려버린다.
  6. "a+", "ab+", 'a+b' - 읽고쓰기. 파일이 있으면 뒤에 추가한다.
  • MSDOS/Windows는 텍스트랑 바이너리 구분이 있는데, 리눅스는 그딴거 없다. b 대신 t를 써도 완전히 똑같이 동작한다.
  • 주의할점이 있는데, 1글자라고 로 싸면 안된다. 꼭 ""으로 쓰자.

fread[편집]

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
  • fread</s>프레드</s> 함수는 파일에서 데이터를 읽을때 쓴다.
  • ptr이 가리키는 데이터 버퍼의 위치로 읽는다.
  • fread는 읽을 단위 사이즈랑, 몇번 읽을지를 nitems로 쓴다.
  • 읽기에 성공하면 몇번 읽었는지를 반환한다.

fwrite[편집]

#include <stdio.h>

size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
  • fwrite는 fread랑 비슷하다.
  • 쓰기에 성공하면 몇번 썼는지는 반환한다.
  • 이식성에 문제가 있을수 있다고는 한다지만, 난 잘 모르것당

fclose[편집]

#include <stdio.h>

int fclose(FILE *stream);
  • fclose는 버퍼링되고 있는 파일이 있으면 다 쓰고 파일을 닫는다.
  • 버퍼링이 남아있는데 안닫으면 에러나기가 존나 쉽다. 그리고 늦게 퇴근하겠지꼭 닫도록 하자.

fflush[편집]

#include <stdio.h>

int fflush(FILE *stream);
  • 버퍼된 데이터를 전부 쓰도록 만든다.
  • 이거 알고있으면 생각보다 유용한 상황이 있다.

fseek[편집]

#include <stdio.h>

int fseek(FILE *stream, long int offset, int whence);
  • fseek 함수는 lseek같은짓을 한다. offset과 whence는 lseek랑 같다.
  • 마찬가지로 성공하면 0, 실패하면 -1을 반환한다.
  • 그리고 실패하면희소식은 개뿔. 뭔 에란지 볼 수 있다. errorno가 설정된다.

fget, getc, getchar[편집]

#include <stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();
  • fgetc는 하나의 문자, 현재 위치의 다음 한 바이트를 반환한다. 더이상 읽을게 없으면 EOF를 반환한다.
  • getc는 fgetc랑 똑같다.
  • getchar 함수는 fgetc(stdin)이랑 동일하다. 키보드로 입력 받는거다.

fput, putc, putchar[편집]

#include <stdio.h>

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
  • fputc는 하나의 문자, 현재 위치의 다음에 한 바이트를 쓴다. 반환값은 쓴 바이트를 반환하고, 실패하면 EOF를 반환한다.
  • fgetc는 fputc랑 똑같다.
  • putchar는 fputc(c, stdout)이랑 동일하다.

fgets, gets[편집]

#include <stdio.h>

char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
  • fgets 함수는 문자열을 읽는다. 이건 문자들을 새로운 라인을 나타내는 문자(\n)을 만날때까지 s에다가 내용을 써넣는다.
  • n-1개의 문자가 전송되거나 EOF나 \n을 만나서 더 적게 읽어들이는 경우가 있다.
  • n-1인 이유는 마지막에 \0을 붙이기 때문이다
  • 성공적으로 수행되면 fgets는 문자열에 대한 포인터를 반환한다.
  • 파일이 EOF에 도달해 더 읽을 수 없는 경우 널 포인터를 반환하고, errno를 설정한다.
  • gets함수는 표준입력에서 읽어들인다.

printf, fprintf, sprintf[편집]

#include <stdio.h>

int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
  • printf류의 함수는 다양한 타입 변수의 값을 형식화하여 출력한다. 각각의 변수값을 출력스트림에 나타내는 방식은 format 매개인자에서 제어하게 되는데, 일반적인 문자열과 출력방법과 위치를 지정하는 변환 식별자로 구성된다.
  • printf 함수는 표준 출력에 내용을 보낸다.존나 많이 써봤을테니 자세한 설명은 생략한다.
  • fprintf 함수는 출력될 대상을 지정하는것 빼곤 printf랑 같다.
  • sprintf는 출력대상이 s다.
  • 일반적인 문자는 변환되지 않는다. 변환 식별자는 항상 %로 시작된다.
  •  %를 출력하기 위해서는 %%를 입력한다.
  • 변환 식별자의 종류
  1.  %d, %i - 10진수 정수
  2.  %o - 8진수 정수
  3.  %x - 16진수 정수
  4.  %c - 캐릭터(문자)
  5.  %f - 단정도 부동소수점
  6.  %e - 배정도 부동소수점
  7.  %g - double 자료형
  • printf에 넘겨주는 인자의 갯수랑 타입은 format 문자열에 있는 변환 식별자의 갯수, 타입과 일치해야 한다.
  • 변환 식별자에서 short int를 표시하기 위해 %hd 변환 식별자를 사용할 수 있다.
  • 변환 식별자에서 long int를 표시하기 위해 %ld 변환 식별자를 사용할 수 있다. 64비트의 경우 기본값이다.
  • 컴파일러마다 약간씩 다르므로 gcc가 아닌 다른 컴파일러를 이용할 경우 그쪽 문서를 참고바란다.

scanf, fscanf, sscanf[편집]

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int scnaf(const char *s, const char *format, ...);
  • scanf류의 함수는 printf류와 비슷하지만, 변수를 포인터로 받는다는 점이 다르다.
  • 마찬가지로 입력변환을 제어하기 위해 식별자를 사용한다.
  • scanf 함수를 이용하여 읽어들인 값을 저장할 변수는 올바른 타입이어야 한다.이래서 scanf 사용성 문제가 있다. 사용자는 다 미친놈이라서... 걸핏하면 런타임 에러..
  • scanf류의 함수에 사용되는 변환 식별자 이외에 문자열은 입력시 무시된다.
  • 다음과 같은 이유로 scanf 함수는 추천하기가 어렵다.쓰지마라
  1. 에러처리가 어렵다.
  2. 사용에 유연성이 없다.
  3. 파싱 작업을 할 때 죽어난다.

다른 표준 입출력 함수[편집]

  1. fgetpos - 파일 지정자의 현재 위치를 얻어낸다.
  2. fsetpos - 파일 지정자의 현재 위치를 지정한다.
  3. ftell - 파일 오프셋을 반환한다.
  4. rewind - 처음으로 돌아간다.
  5. freopen - 다시 연다
  6. setvbuf - 버퍼링 모드를 지정한다.
  7. remove - 디렉토리가 아니면 rm 기능을 하고, 디렉토리라면 rmdir 기능을 한다.
  • 라이브러리 함수는 맨페이지 섹션 3에 나와있다.

표준 입출력을 이용한 파일복사 프로그램[편집]

  • unistd.h가 아닌 stdio.h를 사용
/* copy_stdio.c */
#include <stdio.h>
int main()
{
  int c;
  FILE *in, *out;

  in = fopen("file.in", "r");
  out = fopen("file.out", "r");

  while((c = fgetc(in)) != EOF)
    fputc(c, out);

  return 0;
}
  • 프로그램 수행 시간을 측정해보면 다음과 같다.
drake@debian:~/cc$ cc -o copy_stdio copy_stdio.c
drake@debian:~/cc$ time ./copy_stdio

real    0m2.467s
user    0m2.328s
sys     0m0.128s
drake@debian:~/cc$
  • read/write보다 stdio를 쓰는게 훨씬 빠르다.

errno, ferror, feof, clearerr[편집]

#include <errno.h>

extern int errno;
  • 전역변수다. 혹자는 전역변수 쓴다고 뭐라할거 같은데.. 이건 좀 봐주자... ㅋㅋㅋ
#include ferror(FILE *stream);

int ferror(FILE *stream);
int feof(FILE *stream);
int clearerr(FILE *stream);
  • ferror는 에러가 났는지 확인하고 에러메세지가 있을 경우 에러 번호를 반환한다.
  • feof는 파일이 끝인지 확인하고 끝이면 0이 아닌 수를 반환하고, 끝이면 0을 반환한다.
  • clearerr 함수는 에러메세지를 지워버리는 함수다. 설계할 때 제대로 설계해야 한다.예외처리는 최대한 없는게 좋다. 진심 존나 어쩔수 없을때 쓰자.

fileno, fdopen[편집]

#include <stdio.h>

FILE *fdopen(int fildes, const_char *mode);
  • fileno는 저수준 시스템콜을 위한 File Descriptor를 반환한다.
  • fdopen은 저수준 시스템콜로 열려있는 File Descriptor를 stdio에서 쓸 수 있는 형식으로 만든다. mode는 fopen 참조.

파일과 디렉토리 관리[편집]

  • bash나 csh 쓸때 많이 보던 명령어들이 나올거다.

chmod[편집]

#include <sys/stat.h>

int chmod(const char *path, mode_t mode);
  • path의 파일을 mode 권한으로 변경한다. mode는 open 참조.

chown[편집]

#include <unistd.h>

int chown(const char *path, uid_t owner, gid_t group);
  • chown은 사용자 ID와 그룹 ID 번호(getuid와 getgid 호출에서 쓰는 번호)를 함수의 인자로 사용한다.

unlink, link, symlink[편집]

#include <unistd.h>

int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);
  • unlink는 파일을 삭제한다. 성공하면 0, 에러는 1 반환. path에 해당하는 파일의 쓰기 권한 없으면 1을 보게 될 것이다.
  • link는 하드링크다. ln 쓸때 -s 옵션 안쓴거라 생각하면 됨.
  • symlink는 심볼릭 링크다. ln 쓸때 -s 옵션 쓸때라고 생각하면 됨.

mkdir, rmdir[편집]

#include <sys/stat.h>

int mkdir(const char *path, mode_t mode);
  • mkdir은 디렉토리를 mode 권한으로 변경한다. 마찬가지로 open 참조.
#include <unistd.h>

int rmdir(const char *path);
  • rmdir은 빈 디렉토리를 지워뿐다. 성공하면 0, 실패하면 1 반환.

chdir, getcwd[편집]

#include <unistd.h>

int chdir(const char *path);
char *getcwd(char *buf, size_t size);
  • chdir은 path로 이동. cd와 같다.
  • getcwd는 현재 디렉토리를 구한다. 제대로 구하면 디렉토리 이름을 넣은 buf 반환.
  • buf는 디렉토리 명칭보다 길어야 하는데, 그보다 짧으면 null(0) 반환.지까짓게 길어봐야 1024바이트면 거의 해결되는듯
  • 권한이 없어도 null을 반환한다.

디렉토리 검색[편집]

  • DIR 구조체는 /usr/include/dirent.h 참조

opendir[편집]

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
  • 성공하면 구조체 넣어서 반환, 실패하면 null(0) 반환

readdir[편집]

#include <sys/types.h>
#include <dirent.h>

struct dirent *readdir(DIR *dirp);
  • readdir은 dirp 디렉토리의 다음 디렉토리 항목을 가리키는 포인터를 반환한다. 디렉토리가 끝나면 null 반환.
  • 여러개 열 경우 제대로 못 읽을 수 있다(가능하면 멀티태스킹 하지 말란 얘기)
  • 디렉토리 안에 파일 내용에 대해서는 stat 함수를 쓸 수 있다.

telldir[편집]

#include <sys/stat.h>
#include <dirent.h>

long int telldir(DIR *dirp);
  • 현재 디렉토리 위치를 반환한다.

seekdir[편집]

#include <sys/types.h>
#include <dirent.h>

void seekdir(DIR *dirp, long int loc);
  • 디렉토리의 현재 위치를 세팅한다. loc의 값은 telldir에서 나온 값을 쓰자.

closedir[편집]

#include <sys/types.h>
#include <dirent.h>

int closedir(DIR *dirp);
  • 파일닫기와 마찬가지로, 디렉토리를 닫고 자원을 반환한다. 성공은 0 반환, 실패는 1 반환.
  • 솔직히 C로 쉘만큼의 유연성을 갖기는 힘들다. 오픈 파일에 제한이 있기 때문에. 하지만 못 하는건 아니니 안심. 어차피 쉘 프로그램도 다 C로 짜져있다.

디렉토리 검색 프로그램[편집]

#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>

void printdir(char *dir, int depth)
{
  DIR *dp;
  struct dirent *entry;
  struct stat statbuf;

  if((dp == opendir(dir)) == NULL) {
    fprintf(stderr, "Cannot open directory: %s\n", dir);
    return;
  }
  chdir(dir);
  while((entry = readdir(dp)) != NULL) {
    stat(entry->d_name, &statbuf);
    if(S_ISDIR(statbuf.st_mmode)) {
      /* 디렉토리를 찾았을때 .이랑 ..은 제외한다 이거 포함하면 존나 무한루프 */
      if(strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)
        continue;
      printf("%*s%s/\n",depth,"",entry->d_name);
      /* 디렉토리 안에 디렉토리가 나오면 */
      printdir(entry->d_name, depth+4);
    }
    else printf("%*s%s\n", depth, "", entry->d_name);
  }
  chdir("..");
  closedir(dp);
}

int main()
{
  printf("Directory scan of /home/drake:\n");
  printdir("/home/drake", 0);
  printf("Done.\n");
  return 0;
}
  • 프로그램 돌리면 find /home/drake랑 비슷하게 출력된다.
  • find 처럼 디렉토리를 지정하려면 main을 바꾼다.
int main(int argc, char* argv[])
{
  char *topdir, pwd[2] = ".";
  if (argc != 2)
    topdir = pwd;
  else
    topdir = argv[1];
  printf("Directory scan of %s\n", topdir);
  printdir(topdir, 0);
  printf("Done.\n");
  return 0;
}

에러[편집]

strerror[편집]

#include <string.h>

char *strerror(int errnum);
  • strerror는 에러번호를 사람이 볼 수 있는 문자열로 바꾼다.

perror[편집]

#include <stdio.h>

void perror(const char *s);
  • perror는 에러를 출력한다. s를 출력하고 그다음에 에러메세지를 출력한다.
  • 즉, perror("program"); 요건 program: Too many open files 라고 출력된다.

다른 함수들[편집]

  • 아래 내용은 잘 쓰이지 않는다. 가능하면 안 쓰는게 좋기도 하고.

fcntl[편집]

#include <fcntl.h>

int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);
  • fcntl(fildes, F_DUPFD, newfd); // dup 함수처럼 쓴다.
  • fcntl(fildes, F_GETFD); // File Descriptor가 지정하는 파일의 상태를 읽어온다.
  • fcntl(fildes, F_SETFD, flags); // File Descriptor가 지정하는 파일의 상태를 변경한다.
  • fcntl(fildes, F_GETFL); // File Descriptor가 지정하는 파일의 접근상태를 가져온다.
  • fcntl(fildes, F_GETFL, flags); // File Descriptor가 지정하는 파일의 접근상태를 flags의 내용으로 변경한다. open 참조.
  • 이외에도, 파일 잠금을 구현할 때 사용한다.

mmap[편집]

#include <sys/mman.h>

void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
  • 메모리 공유를 위한 함수다.
  • open된 파일에 접근이 가능.
  • off로 데이터를 변경할 수 있다.
  • 접근 가능한 데이터의 양은 len으로 세팅
  • prot로 권한 세팅
  1. PROT_READ - 세그먼트 읽기가능
  2. PROT_WRITE - 세그먼트 쓰기가능
  3. PROT_EXEC - 세그먼트 실행가능
  4. PROT_NONE - 세그먼트 접근불가
  • flags로 변환에 대한 세팅
  1. MAP_PRIVATE - 세그먼트는 개별, 변화는 안에서만.
  2. MAP_SHARED - 세그먼트의 변화는 파일 안에서만.
  3. MAP_FIXED - 세그먼트는 addr로 주어진 주소에 존재해야만 함

msync[편집]

#include <sys/mman.h>

int msync(void *addr, size_t len, int flags);
  • 세그먼트를 갱신할 때 쓴다.
  • flags로 어떻게 갱신하는지 제어한다.
  1. MS_ASYNC - 비동기 방식
  2. MS_SYNC - 동기 방식
  3. MS_INVALIDATE - 파일로부터 읽음

munmap[편집]

#include <sys/mman.h>

int munmap(void *addr, size_t len);
  • 세그먼트 해제.

mmap 사용 예제[편집]

#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>

typedef struct {
  int integer;
  char string[12];
} RECORD;

#define NRECORDS (100)

int main()
{
  /* RECORD 구조체를 정의하고, 레코드의 갯수를 NRECORD로 정의한다. 구조체들은 records.dat 파일에 추가된다. */
  RECORD record, *mapped;
  int i, f;
  FILE *fp;

  fp = fopen("records.dat", "W+");
  for(i = 0; i< NRECORDS; i++) {
    record.integer = i;
    sprintf(record.string,"RECORD-%d", i);
    fwrite(&record, sizeof(record), 1, fp);
  }
  fclose(fp);

  /* 레코드의 정수값을 43에서 143으로 변경하고 이것을 43번째 레코드의 문자열에 기록
  fp = fopen("records.dat", "r+");
  fseek(fp, 43*sizeof(record), SEEK_SET);
  fread(&record, sizeof(record), 1, fp);
  record.integer = 143;
  sprintf(record.string, "RECORD-%d", record.integer);
  fseek(fp, 43*sizeof(record), SEEK_SET);
  fwrite(&record, sizeof(record), 1, fp);
  fclose(fp);

  /* 이제 레코드를 메모리로 매핑하고 값을 243으로 변경하기 위해 43번째 레코드에 접근한다. 그리고 레코드 문자열을 갱신, 메모리 매핑을 이용한 방법이다. */
  f = open("records.dat", O_RDWR);
  read(f, &record, sizeof(record));
  mapped = (RECORD *)mmap(0, NRECORDS * sizeof(record), PROT_READ|PROT_WRITE, MAP_SHARED, f, 0);
  mapped[43].integer = 243;
  sprintf(mapped[43].string,"RECORD-%d", mapped[43].integer);

  msync((void *)mapped, NRECORDS * sizeof(record), MS_ASYNC);
  munmap((void *)mapped, NRECORDS * sizeof(record));
  close(f);

  return 0;
}

파일 잠금[편집]

Basic[편집]

/* lock1.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

int main() {
    int file_desc;
    int save_errno;

    file_desc = open("/tmp/LCK.test", O_RDWR | O_CREAT | O_EXCL, 0444);
    if(file_desc == -1) {
      save_errno = errno;
      printf("Open failed with error %d\n", save_errno);
    }
    else {
      printf("Open succeeded\n");
    }
    return EXIT_SUCCESS;
}
  • 위 예제는, 잠금파일을 만들어놓고 잠금파일이 존재하면 에러를 내는 예제다.
/* lock2.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

const char *lock_file = "/tmp/LCK.test2";

int main() {
  int file_desc;
  int tries = 10;

  while (tires--) {
    file_desc = open(lock_file, O_RDWR | O_CREAT | O_EXCL, 0444);
    if(file_desc == -1) {
      printf("%d - Lock already present\n", getpid());
      sleep(3);
    } else {
      printf("%d - I have exclusive access\n", getpid());
      sleep(1);
      (void)close(file_desc);
      (void)unlink(lock_file);
      sleep(2);
    }
  } /* while */
  return EXIT_SUCCESS;
}
  • 자원 공유를 할 때엔 lock을 걸고 하는것이 좋다.

fcntl[편집]

#include <fcntl.h>

int fcntl(int fildes, int command, ...);
int fcntl(int fildes, int command, struct flock *flock_structure);
  • fcntl은 command의 작업을 수행한다. command는 다음과 같다.
  1. F_GETLK
  2. F_SETLK
  3. F_SETLKW
  • flock 구조체는 다음을 포함한다.
  1. short l_type;
  2. short l_whence;
  3. off_t l_start;
  4. off_t l_len;
  5. pid_t l_pid;
  • l_type 멤버는 다음 중 하나의 값을 취한다.
  1. F_RDLCK - 공유 잠금. r / rw 모드에 적용가능
  2. F_UNLCK - 잠금 해제
  3. F_WRLCK - 쓰기 잠금. 프로세스 하나가 잠가놓으면 다른 프로세스는 잠금을 할 수 없다. w / rw 모드에 적용가능
  • l_whence는 다음과 같다.
  1. SEEK_SET - 파일의 시작위치
  2. SEEK_CUR - 파일의 현재 위치
  3. SEEK_END - 파일의 끝 위치
  • l_start는 l_whence에서 지정한 첫 바이트다. 예를들어 l_whence에 SEEK_SET을 썼다면 파일의 첫 부분이다.
  1. l_len은 바이트수를 지정한다.
  2. l_pid는 잠그는 프로세스를 보고하는데 쓴다.
  • F_GETLK는 fildes에 대해 잠금 정보를 얻는다. F_GETLK를 사용하기 위한 flock 구조체에 사용되는 값은 다음과 같다.
  1. l_type - F_RDLCK / F_WRLCK
  2. l_whence - SEEK_SET, SEEK_CUR, SEEK_END 중 하나
  3. l_start - lock 파일 시작 위치
  4. l_len - lock 파일 바이트 수
  5. l_pid - lock과 관련된 pid
  • 프로세스는 F_GETLK를 사용해서 파일이 잠겨있는지, 잠겨있다면 현재 상황이 어떤지 알아볼 수 있다. 알아보길 원하는 형태를 구조체에 넣어두어야 한다.
  • F_GETLK는 성공하면 -1이 아닌 값을 반환하고, 실패하면 -1을 반환한다.
  • 일반적으로 F_GETLK가 성공했을때 잠겨 있는 프로세스가 있는지 확인하기 위해 pid를 확인하는게 일반적이다.
  • F_SETLK는 잠금을 시도하거나 잠금을 해제한다. 이때 관련되는 flock 구조체 멤버는 다음과 같다.
  1. l_type - F_RDLCK, F_WRLCK, F_UNLCK
  • F_SETLK로 잠금이 성공하면 -1이 아닌 값을 반환하고, 실패하면 -1을 반환한다.
  • F_SETLKW는 F_SETLK랑 같지만, 이건 쓰기 가능할때까지 기다린다.좀비 오오 좀비
  • 와닿지 않으니 예제를 보자
/* lock3.c */
/* 필요한 헤더를 포함하고 변수를 선언 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";

int main() {
  int file_desc;
  int byte_count;
  char *byte_to_write = "A";
  struct flock region_1;
  struct flock region_2;
  int res;

  /* 파일을 연다. */
  file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
  if(!file_desc) {
      fprintf(stderr, "Unable to open %s for read/write\n", test_file);
      return EXIT_FAILURE;
  }

  /* 파일에 데이터 입력 */
  for(byte_count = 0; byte_count < 100; byte_count++) {
    (void)write(file_desc, byte_to_write, 1);
  }

  /* region_1을 10바이트에서 30바이트까지 공유잠금 */
  region_1.l_type = F_RDLCK;
  region_1.l_whence = SEEK_SET;
  region_1.l_start = 10;
  region_1.l_len = 20;

  /* region_2를 40바이트에서 50바이트까지 독점잠금 */
  region_2.l_type = F_WRLCK;
  region_2.l_whence = SEEK_SET;
  region_2.l_start = 40;
  region_2.l_len = 10;

  /* 파일 잠금 프로세스 실행 */
  printf("Process %d locking file\n", getpid());
  res = fcntl(file_desc, F_SETLK, &region_1);
  if(res == -1) fprintf(stderr, "Failed to lock region 1\n");
  res = fcntl(file_desc, F_SETLK, &region_2);
  if(res == -1) fprintf(stderr, "Failed to lock region 2\n");

  /* 1분간 대기 */
  sleep(60);

  printf("Process %d closing file\n", getpid());
  close(file_desc);
  return EXIT_SUCCESS;
}
  • 이건 그냥 잠금을 보여주는 예제고, 다음은 테스트를 해보자.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";
#define SIZE_TO_TRY 5

void show_lock_info(struct flock *to_show);

int main() {
  int file_desc;
  int res;
  struct flock region_to_test;
  int start_byte;

  /* 파일을 연다. */
  file_desc = open(test_file, O_RDWR|O_CREAT, 0666);
  if(!file_desc) {
    fprintf(stderr, "Unable to open %s for read/write", test_file);
    return EXIT_FAILURE;
  }

  for(start_byte = 0; start_byte < 99; start_byte += SIZE_TO_TRY) {

    /* 테스트 region 설정 */
    region_to_test.l_type = F_WRLCK;
    region_to_test.l_whence = SEEK_SET;
    region_to_test.l_start = start_byte;
    region_to_test.l_len = SIZE_TO_TRY;
    region_to_test.l_pid = -1;

    printf("Testing F_WRLCK on region from %d to %d\n", start_byte, start_byte + SIZE_TO_TRY);

    /* 파일 잠금 테스트 */
    res = fcntl(file_desc, F_GETLK, &region_to_test);
    if(res == -1) {
      fprintf(stderr, "F_GETLK failed\n");
      return EXIT_FAILURE;
    }
    if(region_to_test.l_pid != -1) {
      printf("Lock would fail. F_GETLK returned:\n");
      show_lock_info(&region_to_test);
    } else {
      printf("F_WRLCK - Lock would succeed\n");
    }

    /* 읽기잠금 테스트 반복. 테스트 region을 재설정 */
    region_to_test.l_type = F_RDLCK;
    region_to_test.l_whence = SEEK_SET;
    region_to_test.l_start = start_byte;
    region_to_test.l_len = SIZE_TO_TRY;
    region_to_test.l_pid = -1;

    printf("Testing F_RDLCK on region from %d to %d\n", start_byte, start_byte + SIZE_TO_TRY);

    /* 파일 잠금 테스트 */
    res = fcntl(file_desc, F_GETLK, &region_to_test);
    if(res == -1) {
      fprintf(stderr, "F_GETLK failed\n");
      return EXIT_FAILURE;
    }
    if(region_to_test.l_pid != -1) {
      printf("Lock would fail. F_GETLK returned:\n");
      show_lock_info(&region_to_test);
    } else {
      printf("F_RDLCK - Lock would succeed\n");
    }
  } /* for */
  close(file_desc);
  return EXIT_SUCCESS;
}

void show_lock_info(struct flock *to_show) {
  printf("\tl_type %d, ", to_show->l_type);
  printf("l_whence %d, ", to_show->l_whence);
  printf("l_start %d, ", (int)to_show->l_start);
  printf("l_len %d, ", (int)to_show->l_len);
  printf("l_pid %d\n", to_show->l_pid);
}
  • lock3과 lock4를 같이 돌려보면 좀더 이해가 쉬울거다.
$ ./lock3 &
  • 이렇게 백그라운드로 lock3을 돌려놓고 lock4를 실행하면 잠긴 부분 접근에 대해 볼 수 있다.
/* lock5.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

const char *test_file = "/tmp/test_lock";

int main() {
  int file_desc;
  struct flock region_to_lock;
  int res;

  /* 파일을 연다 */
  file_desc = open(test_file, O_RDWR|O_CREAT, 0666);
  if(!file_desc) {
    fprintf(stderr, "Unable to open %s for read/write\n", test_file);
    return EXIT_FAILURE;
  }
  region_to_lock.l_type = F_RDLCK;
  region_to_lock.l_whence = SEEK_SET;
  region_to_lock.l_start = 10;
  region_to_lock.l_len = 5;
  printf("Process %d, trying F_RDLCK, region %d to %d\n", getpid(), (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
  res = fcntl(file_desc, F_SETLK, &region_to_lock);
  if(res == -1) {
    printf("Process %d - failed to lock region\n", getpid());
  } else {
    printf("Process %d - obtained lock region\n", getpid());
  }

  region_to_lock.l_type = F_UNLCK;
  region_to_lock.l_whence = SEEK_SET;
  region_to_lock.l_start = 10;
  region_to_lock.l_len = 5;
  printf("Process %d, trying F_UNLCK, region %d to %d\n", getpid(), (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
  res = fcntl(file_desc, F_SETLK, &region_to_lock);
  if(res == -1) {
    printf("Process %d - failed to unlock region\n", getpid());
  } else {
    printf("Process %d - unlocked region\n", getpid());
  }

  region_to_lock.l_type = F_WRLCK;
  region_to_lock.l_whence = SEEK_SET;
  region_to_lock.l_start = 16;
  region_to_lock.l_len = 5;
  printf("Process %d, trying F_WRLCK, region %d to %d\n", getpid(), (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
  res = fcntl(file_desc, F_SETLK, &region_to_lock);
  if(res == -1) {
    printf("Process %d - failed to lock region\n", getpid());
  } else {
    printf("Process %d - obtained lock on region\n", getpid());
  }

  region_to_lock.l_type = F_RDLCK;
  region_to_lock.l_whence = SEEK_SET;
  region_to_lock.l_start = 40;
  region_to_lock.l_len = 10;
  printf("Process %d, trying F_RDLCK, region %d to %d\n", getpid(), (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
  res = fcntl(file_desc, F_SETLK, &region_to_lock);
  if(res == -1) {
    printf("Process %d - failed to lock region\n", getpid());
  } else {
    printf("Process %d - obtained lock on region\n", getpid());
  }

  region_to_lock.l_type = F_WRLCK;
  region_to_lock.l_whence = SEEK_SET;
  region_to_lock.l_start = 16;
  region_to_lock.l_len = 5;
  printf("Process %d, trying F_WRLCK with wait, region %d to %d\n", getpid(), (int)region_to_lock.l_start, (int)(region_to_lock.l_start + region_to_lock.l_len));
  res = fcntl(file_desc, F_SETLKW, &region_to_lock);
  if(res == -1) {
    printf("Process %d - failed to lock region\n", getpid());
  } else {
    printf("Process %d - obtained lock on region\n", getpid());
  }

  printf("Process %d ending\n", getpid());
  close(file_desc);
  return EXIT_SUCCESS;
}
  • lock5도 마찬가지로 lock3를 실행해두고 실행하면 좀더 이해가 쉽다.

lockf[편집]

#incldue <unistd.h>

int lockf(int fildes, int function, off_t size_to_lock);
  • function은 다음중 하나로 취할 수 있다.
  1. F_UNLOCK - 잠금 해제
  2. F_LOCK - 잠금
  3. F_TLOCK - 테스트 후 잠금
  4. F_TEST - 잠겨있는지 테스트
  • fcntl보다 단순하게 사용할 수 있다.

Socket[편집]

Basic local client[편집]

/* client1.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
  int sockfd;
  int len;
  struct sockaddr_un address;
  int result;
  char ch = 'A';

  /* 클라이언트 소켓 생성 */
  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

  /* 서버와 일치하는 이름을 소켓에 부여한다. */
  address.sun_family = AF_UNIX;
  strcpy(address.sun_path, "server_socket");
  len = sizeof(address);

  /* 클라이언트 소켓을 서버 소켓에 꼽는다 */
  result = connect(sockfd, (struct sockaddr *)&address, len);

  if(result == -1) {
    perror("oops: client1");
    return 1;
  }

  write(sockfd, &ch, 1);
  read(sockfd, &ch, 1);
  printf("char from server = %c\n", ch);
  close(sockfd);
  return 0; 
}
  • 이것만 가지고는 제대로 되지 않는다.

Basic local server[편집]

/* server1.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_un server_address;
  struct sockaddr_un client_address;

  /* 이전 소켓을 제거하고 새로운 소켓 생성 */
  unlink("server_socket");
  server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

  /* 소켓 이름 부여 */
  server_address.sun_family = AF_UNIX;
  strcpy(server_address.sun_path, "server_socket");
  server_len = sizeof(server_address);
  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  /* 연결 큐를 만들고 클라이언트를 기다린다. */
  listen(server_sockfd, 5);
  while(1) {
    char ch;

    printf("Server waiting\n");

    /* 접속 허용 */
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

    /* 이제 client_sockfd를 통해 클라이언트에게 read/write할 수 있다. */
    read(client_sockfd, &ch, 1);
    ch++;
    write(client_sockfd, &ch, 1);
    close(client_sockfd);
  }
}

socket, bind, listen, accept, connect[편집]

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain은 다음중 하나를 사용할 수 있다.
  1. AF_UNIX - 유닉스(파일시스템)
  2. AF_INET - ARPA 인터넷 프로토콜(유닉스 네트워크)
  3. AF_NS - Xerox Network System 프로토콜
  • type은 소켓타입을 나타낸다.
  1. SOCK_STREAM - TCP/IP
  2. SOCK_DGRAM - UDP/IP
  • 파일시스템을 이용하는 sockaddr_un의 내용
struct sockaddr_un {
  sa_family_t sun_family; /* AF_UNIX */
  char        sun_path[]; /* pathname */
};
  • 네트워크시스템을 이용하는 sockaddr_in의 내용
struct sockaddr_in {
  short int          sin_family; /* AF_UNIX */
  unisgned short int sin_port;   /* port number */
  struct in_addr     sin_addr;   /* Internet address */
};
struct in_addr {
  unsigned long int s_addr;
};
  • 소켓을 다른 프로세스가 사용가능하게 만들려면, 소켓에 이름을 붙여야 한다.
  • AF_UNIX는 경로명, AF_INET은 IP포트와 관련시키는것을 의미한다.
#include <sys/socket.h>

int bind(int socket, cost struct sockaddr *address, size_t address_len);
  • bind는 성공시 0 실패시 -1을 반환하고, errno는 다음중 하나.
  1. EBADF - File Descriptor가 이상하다.
  2. ENOTSOCK - File Descriptor가 socket과 관련이 없다.
  3. EINVAL - File Descriptor가 이미 소켓을 참조하고 있다.
  4. EADDRNOTAVAIL - 주소가 잘못됨
  5. EADDRINUSE - 해당 주소를 사용할 수 없음
  6. EACCESS - 권한없음
  7. ENOTDIR - 파일이름이 잘못됨
  8. ENAMETOOLONG - 파일이름이 너무 길다
#include <sys/socket.h>

int listen(int socket, int backlog);
  • 요청을 저장할 큐를 연다.
  • 큐의 길이는 backlog로 지정.
  • listen이 성공하면 0, 실패시 -1 반환. errno는 다음중 하나.
  1. EBADF - File Descriptor가 이상하다.
  2. EINVAL - File Descriptor가 이미 소켓을 참조하고 있다.
  3. ENOTSOCK - 소켓이 잘못됨
#include <sys/socket.h>

int accept(int socket, strcut sockaddr *address, size_t *address_len);
  • accept는 클라이언트와 통신할 소켓을 생성하고 File Descriptor를 반환한다.
  • 소켓은 이전에 bind로 명명되어야 하고, listen을 사용하여 접속 큐를 만들어야 한다.
  • accept는 새로 요청이 들어올때까지 블록되는데, fcntl로 O_NONBLOCK으로 바꿀수 있다.
  • 접속요청시 File Descriptor를 반환하고, 에러나면 -1을 반환한다.
  • fcntl로 NONBLOCK 모드일 경우 EWOULDBLOCK이 발생할 수 있다.
#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, size_t address_len);
  • socket은 클라이언트가 서버측이 열어놓은 listen에서 address를 이용해 연결한다.
  • connect는 성공하면 0, 에러시에는 -1을 반환하고, errno는 다음과 같다.
  1. EBADF - File Descriptor가 이상하다.
  2. EALREADY - 소켓을 누가 먼저 잡고있다.
  3. ETIMEOUT - 접속시간이 지났다.
  4. ECONNREFUSED - 서버가 접속을 거절했다.
  5. EINTR - 인터럽트에 걸렸다.
  6. EINPROGRESS - 동기연결 실패, 비동기로 접속 가능할수 있다?
  • 소켓은 close로 닫을 수 있다.

Basic network client[편집]

/* client2.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{
  int sockfd;
  int len;
  struct sockaddr_in address;
  int result;
  char ch = 'A';

  /* 클라이언트에서 사용할 소켓 생성 */
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  /* 서버와 일치하도록 소켓에 명칭 부여 */
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = inet_addr("127.0.0.1");
  address.sin_port = 9734;
  len = sizeof(address);

  /* 클라이언트 소켓을 서버 소켓에 꼽는다 */
  result = connect(sockfd, (struct sockaddr *)&address, len);
 
  if(result == -1) {
    perror("oops: client1");
    return 1;
  }
 
  write(sockfd, &ch, 1);
  read(sockfd, &ch, 1);
  printf("char from server = %c\n", ch);
  close(sockfd);
  return 0; 
}

Basic network server[편집]

/* server2.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_in server_address;
  struct sockaddr_in client_address;

  /* 서버에서 사용할 소켓 생성 */
  server_sockfd = socket(AF_INET, SOCK_STREAM, 0);;

  /* 소켓 명칭 부여 */
  server_Address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
  server_address.sin_port = 9734;
  server_len = sizeof(server_address);
  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  /* 연결 큐를 만들고 클라이언트를 기다린다. */
  listen(server_sockfd, 5);
  while(1) {
    char ch;
 
    printf("Server waiting\n");
 
    /* 접속 허용 */
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
 
    /* 이제 client_sockfd를 통해 클라이언트에게 read/write할 수 있다. */
    read(client_sockfd, &ch, 1);
    ch++;
    write(client_sockfd, &ch, 1);
    close(client_sockfd);
  }
}

htonl, htons, ntohl, ntohs[편집]

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
  • h, nl, ns는 hostname, network long, network short다.

gethostbyaddr, gethostbyname[편집]

#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr, size_t len, int type);
struct hostent *gethostbyname(const char *name);
  • hostent는 다음과 같다.
struct hostent {
  char *h_name;       /* 호스트 이름 */
  char **h_aliases;   /* alias 이름 */
  int h_addrtype;     /* address 형태 */
  int h_length;       /* address 길이 */
  char **h_addr_list; /* address 목록(network order) */

getservbyname, getservbyport[편집]

#include <netdb.h>

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
  • proto는 SOCK_STREAM이 tcp, SOCK_DGRAM은 udp다.
  • servent는 다음과 같다.
struct servent {
  char *s_name;     /* 서비스 이름 */
  char **s_aliases; /* alias 이름 */
  int s_port;       /* IP 포트번호 */
  char *s_proto;    /* 서비스타입. tcp나 udp */
  • gethostbyname으로 호스트 db정보 수집, 결과를 출력할 수 있다.
  • 주소타입은 타입캐스트해야 한다.

inet_ntoa[편집]

#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
  • 인터넷 호스트 주소를 익숙한 xx.xx.xx.xx 문자열로 변환한다. 에러시 -1을 반환한다.
#include <unistd.h>

int gethostname(char *name, int namelength);
  • 현재 호스트의 이름을 name에 쓴다. 성공하면 0, 에러시 -1을 반환한다.

네트워크 정보[편집]

/* getname.c */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
  char *host, **names, **addrs;
  struct hostent *hostinfo;

  /* arg가 있으면 그걸 호스트이름으로 하고, 아니면 사용자 머신을 gethostname으로 구한다. */
  if(argc == 1) {
    char myname[256];
    gethostname(myname, 255);
    host = myname;
  }
  else
    host = argv[1];

  /* gethostname으로 호스트에 대한 정보를 구하고 실패하면 에러 출력 */
  hostinfo = gethostbyname(host);
  if(!hostinfo) {
    fprintf(stderr, "cannot get info for host: %s\n", host);
    return 1;
  }

  /* 호스트 이름과 alias를 출력한다. */
  printf("result for host %s:\n", host);
  printf("Name: %s\n", hostinfo->h_name);
  printf("Aliases:");
  names = hostinfo->h_aliases;
  while(*names) {
    printf(" %s", *names);
    names++;
  }
  printf("\n");

  /* 호스트가 IP가 아니면 에러 */
  if(hostinfo->h_addrtype != AF_INET) {
    fprintf(stderr, "not an IP host!\n");
    return 1;
  }

  /* IP 어드레스 출력 */
  addrs = hostinfo->h_addr_list;
  while(*addrs) {
    printf(" %s", inet_ntoa(*(struct in_addr *)*addrs));
    addrs++;
  }
  printf("\n");
  return 0;
}

daytime 접속[편집]

/* getdata.c */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
  char *host;
  int sockfd;
  int len, result;
  struct sockaddr_in address;
  struct hostent *hostinfo;
  struct servent *servinfo;
  char buffer[128];

  if(argc == 1)
    host = "localhost";
  else
    host = argv[1];

  /* 호스트 주소를 찾고, 없으면 에러를 출력 */
  hostinfo = gethostbyname(host);
  if(!hostinfo) {
    printf(stderr, "no host: %s\n", host);
    return 1;
  }

  /* 호스트에서 daytime 서비스가 있는 체크 */
  servinfo = getservbyname("daytime", "tcp");
  if(!servinfo) {
    fprintf(stderr, "no daytime service\n");
    return 1;
  }

  /* 소켓 작성 */
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  /* 접속 주소를 설정 */
  address.sin_family = AF_INET;
  address.sin_port = servinfo->s_port;
  address.sin_addr = *(struct in_addr *)*hostinfo->h_addr_list;
  len = sizeof(address);

  /* 접속하고 정보를 얻는다. */
  result = connect(sockfd, (struct sockaddr *)&address, len);
  if(result == -1) {
    perror("oops: getdate");
    return 1;
  }

  result = read(sockfd, buffer, sizeof(buffer));
  printf("read %d bytes: %s\n", result, buffer);

  close(sockfd);
  return 0;
}
  • daytime 서비스가 거의 없다.존나 옛날거란 얘기
  • 작성중이었을때 time-b.nist.gov만이 작동했다.

setsockopt[편집]

#include <sys/socket.h>

int setsockopt(int socket, int level, int option_name, const void *option_value, size_t option_len);
  • 소켓수준으로 세팅하려면 level에 SOL_SOCKET으로 설정해야 한다.
  • 성공시에는 0, 에러나면 -1을 반환한다.

다중클라이언트[편집]

/* server4.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_in server_address;
  struct sockaddr_in client_address;

  server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  server_address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  server_len = sizeof(server_address);
  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  /* 접속에 필요한 대기 큐를 만들고 종료는 무시하고 클라이언트를 기다린다 */
  listen(server_sockfd, 5);

  signal(SIGCHLD, SIG_IGN);

  while(1) {
    char ch;
    printf("server waiting\n");

    /* 접속요청을 받아들임 */
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

    /* 접속된 클라이언트랑 통신할 새로운 자식프로세스를 생성 */
    if(fork() == 0) {
      /* 자식프로세스는 클라이언트와 데이터를 교환하기 위해 client_sockfd상에 읽기/쓰기를 수행한다. 여유를 주기 위하여 중간에 5초간 쉰다. */
      read(client_sockfd, &ch, 1);
      sleep(5);
      ch++;
      write(client_sockfd, &ch, 1);
      close(client_sockfd);
      return 0;
    }
    else {
      close(client_sockfd);
    }
  }
}

FD_ZERO, FD_CLR, FD_SET, FD_ISSET[편집]

  • 블록을 피하려면 fd_set을 세팅해야 한다.
#include <sys/types.h>
#include <sys/time.h>

void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
  • FD_ZERO는 fd_set은 빈상태로 초기화한다.
  • FD_SET, FD_CLR은 집합을 세팅하거나 클리어한다.
  • FD_ISSET은 fd가 fdset 안에 있다면 0이 아닌 숫자를 반환한다.
  • 타임아웃을 위한 timeval구조체는 다음과 같다.
struct timeval {
  time_t tv_sec; /* 초 */
  long tv_usec;  /* 밀리초 */
};

select[편집]

#include <sys/types.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
  • nfds는 테스트할 File Descriptor의 숫자를 명시한다.
  • select는 readfds중에 하나가 읽기준비가 되었던가, writefds중에 하나가 되었던가, errorfds 중 하나가 에러를 갖고 있다거나 하면 리턴
  • 아무런 일도 없을 경우 timeout에 값이 세팅되어 있으면 세팅된 값까지, timeout 세팅이 안되어 있으면 무한정 대기한다.
  • select는 발생한 갯수를 반환하고, 에러일 경우 -1. errno는 다음중 하나
  1. EBADF - File Descriptor가 유효하지 않음
  2. EINTR - 실행도중 인터럽트
  3. EINVAL - nfds나 timeout이 잘못 세팅됐을 경우
/* select.c */
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main()
{
  char buffer[128];
  int result, nread;

  fd_set inputs, testfds;
  struct timeval timeout;

  FD_ZERO(&inputs);
  FD_SET(0, &inputs);

  /* 최대 2.5초동안 표준 입력에서 입력을 기다린다. */
  while(1) {
    testfds = inputs;
    timeout.tv_sec = 2;
    timeout.tv_usec = 500000;

    result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, &timeout);

    /* 시간이 경과한 후 result의 값을 알아본다. 입력이 없었다면 루프를 계속 돌고, 에러가 발생하면 종료한다. */
    switch(result) {
    case 0:
      printf("timeout\n");
      break;
    case -1:
      perror("select");
      return 1;

    default:
      if(FD_ISSET(0, &testfds)) {
        ioctl(0, FIONREAD, &nread);
        if(nread == 0) {
          printf("keyboard done\n");
          return 0;
        }
        nread = read(0, buffer, nread);
        buffer[nread] = 0;
        printf("read %d from keyboard: %s", nread, buffer);
      }
      break;
    }
  }
}

select 서버[편집]

/* server5.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main()
{
  int server_sockfd, client_sockfd;
  int server_len, client_len;
  struct sockaddr_in server_address;
  struct sockaddr_in client_address;
  int result;
  fd_set readfds, testfds;

  /* 서버에서 작동할 소켓을 만들고 명칭을 부여 */
  server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

  server_address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  server_address.sin_port = htons(9734);
  server_len = sizeof(server_address);

  bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

  /* 연결큐를 만들고 server_sockfd에서 입력을 처리하기 위해 readfds를 초기화 */
  listen(server_sockfd, 5);

  FD_ZERO(&readfds);
  FD_SET(server_sockfd, &readfds);

  /* 클라이언트로부터 연결요청을 기다린다. timeout에 널포인터를 전달하므로 타임아웃은 발생하지 않는다. select가 1보다 작은 값을 반환할 경우 프로그램은 종료하고 에러를 출력한다. */
  while(1) {
    char ch;
    int fd;
    int nread;

    testfds = readfds;

    printf("server waiting\n");
    result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *) 0);
    if(result < 1) {
      perror("server5");
      return 1;
    }

    /* 어느 소켓이 활성화되었는지 알아본다 */
    for(fd = 0; fd < FD_SETSIZE; fd++) {
      if(FD_ISSET(fd, &testfds)) {

        /* server_sockfd가 활성화되면 새로운 연결요청을 받아들여야 하고, 관련된 client_sockfd를 집합에 추가한다. */
        if(fd == server_sockfd) {
          client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
          FD_SET(client_sockfd, &readfds);
          printf("adding client on fd %d\n", client_sockfd);
        }

        /* 서버가 활성화된게 아니면 클라이언트가 활성화된것이다. 클라이언트에서 close하면 기술자 집합에서 클라이언트를 제거한다. 다른 경우는 클라이언트와 통신한다. */
        else {
          ioctl(fd, FIONREAD, &nread);

          if(nread == 0) {
            close(fd);
            FD_CLR(fd, &readfds);
            printf("removing client on fd %d\n", fd);
          }

          else {
            read(fd, &ch, 1);
            sleep(5);
            printf("serving client on fd %d\n", fd);
            ch++;
            write(fd, &ch, 1);
          }
        }
      }
    }
  }
}