"File4developers"의 두 판 사이의 차이

깊이있는 삽질 Ubuntu Korea Community Wiki
이동: 둘러보기, 검색
(표준 입출력을 이용한 파일복사 프로그램)
(Write)
63번째 줄: 63번째 줄:
 
* open이라는 시스템 콜을 사용하면 파일이나 장치에 대해 다른 File Descriptor를 연결시킬 수 있다. 근데 자동으로 열려진 File Descriptor에 write를 사용하여 몇가지 단순한 프로그램을 생성할 수 있다.
 
* open이라는 시스템 콜을 사용하면 파일이나 장치에 대해 다른 File Descriptor를 연결시킬 수 있다. 근데 자동으로 열려진 File Descriptor에 write를 사용하여 몇가지 단순한 프로그램을 생성할 수 있다.
 
=== Write ===
 
=== Write ===
<source lang="c">
+
<SyntaxHighlight lang="c">
 
/* write */
 
/* write */
 
#include <unistd.h>
 
#include <unistd.h>
 
size_t write(int fildes, const void *buf, size_t nbytes);
 
size_t write(int fildes, const void *buf, size_t nbytes);
</source>
+
</SyntaxHighlight>
 
* write 시스템 콜은 filedes라는 File Descriptor와 관계된 파일에 쓰기 위해 buf로부터 첫 nbytes만큼의 바이트 수를 읽는다. 이건 진짜 write할 바이트의 수를 되돌려준다. File Descriptor에 에러가 있었다거나 write하려는 디바이스 드라이버가 블록 크기에 상관이 있다면 nbytes 이하가 될 수도 있다. 함수가 0을 반환하면, 파일의 끝(EOF)에 도달했다는 것이고, -1이라면 에러가 있었다는 것이며, 그 에러가 뭔 지 볼 수 있다.
 
* write 시스템 콜은 filedes라는 File Descriptor와 관계된 파일에 쓰기 위해 buf로부터 첫 nbytes만큼의 바이트 수를 읽는다. 이건 진짜 write할 바이트의 수를 되돌려준다. File Descriptor에 에러가 있었다거나 write하려는 디바이스 드라이버가 블록 크기에 상관이 있다면 nbytes 이하가 될 수도 있다. 함수가 0을 반환하면, 파일의 끝(EOF)에 도달했다는 것이고, -1이라면 에러가 있었다는 것이며, 그 에러가 뭔 지 볼 수 있다.
<source lang="c">
+
<SyntaxHighlight lang="c">
 
/* simple_write.c */
 
/* simple_write.c */
 
#include <unistd.h>
 
#include <unistd.h>
79번째 줄: 79번째 줄:
 
   return 0;
 
   return 0;
 
}
 
}
 +
</SyntaxHighlight>
 +
 
* 이 프로그램은 단순히 표준 출력(콘솔)로 메세지를 낸다. 프로그램이 종료될 때 모든 열려있는 File Descriptor는 자동으로 닫히니까, 우린 파일을 닫아야 할 필요는 없지만, 안 쓰면 닫는게 좋다. 버퍼링된 출력을 할땐 걍 열어놓고 종료하면 망한다.
 
* 이 프로그램은 단순히 표준 출력(콘솔)로 메세지를 낸다. 프로그램이 종료될 때 모든 열려있는 File Descriptor는 자동으로 닫히니까, 우린 파일을 닫아야 할 필요는 없지만, 안 쓰면 닫는게 좋다. 버퍼링된 출력을 할땐 걍 열어놓고 종료하면 망한다.
<source lang="bash">
+
 
 +
<SyntaxHighlight lang="bash">
 
$ sudo ./simple_write
 
$ sudo ./simple_write
 
Here is some data
 
Here is some data
 
$
 
$
</source>
+
</SyntaxHighlight>
 +
 
 
=== Read ===
 
=== Read ===
 
<source lang="c">
 
<source lang="c">

2014년 6월 14일 (토) 08:28 판

  • 여기서는 리눅스의 파일과 디렉토리를 살펴보고, 그 파일과 디렉토리를 방법에 대해 알아보겠다. 파일 만들기, 열기, 읽기, 쓰기, 그리고 닫기에 대해 알려줄것이며, 예제를 통해 디렉토리를 생성하고 찾고 지우는 일련의 조작방법도 알아볼거다.근데 나도 잘 몰라
  • 리눅스 파일 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 참조.

파일과 디렉토리 관리