본문 바로가기

CS/유닉스프로그래밍

3-1. 유닉스 파일 : redirection, 표준 IO 라이브러리, error handling

input redirection

$이 뜨고 명령어를 치면 default로 0이 잡혀있음 ( 0은 키보드, 1는 스크린 )

키보드로 입력하면 0번 파일디스크립터로 입력하는 걸 볼 수 있음

 

n = read( 0, buffer, 100 )하면 키보드에서 한 바이트씩 읽어 들임

write(1, buffer, n) 하면 buffer에 있는 내용을 읽은 만큼 스크린에 출력해줌

 

키보드에서 데이터를 읽지 않고 infile에서 input을 받는 것을 redirection이라고 함

 

$ prog_name < infile 

이 프로그램 실행 전에 shell에서 이걸 하면 input을 키보드에서 infile로 바꿔줌

 

dup2(infile, 0); /* infile ==3 */

infile의 내용을 0번이 가리키는 키보드에 dup2하는 거임

infile이 가리키는 포인터로 overwrite하는 거임

 

여기서 infile은 파일 이름이 아니라 파일디스크립터 변수 이름임

$ prog_name < infile 이 명령어에 들어가는 infile은 파일 명인데

dup2에 들어가는 infile은 파일디스크립터 변수이름임

 

infile = open("infile",) 

파일디스크립터를 3번이라고 하면 3번이 가리키는 file table은 infile의 브이노드를 가리킴

 

0번 파일디스크립터가 가리키는 내용을 키보드가 아니라 infile로 바꾸는 것이 input redirectiion임


 

output redirection

write(1,buffer,n)

1번은 스크린임. buffer에 있는 n byte를 스크린에 출력

 

$ prog_name > outfile

여기서에서 말하는 outfile은 파일 이름이고

dup2(outfile,1)

여기에서 말하는 outfile은 파일디스크립터 이름임

이 파일디스크립터 outfile이 3번이라고 치면

 

1번이 이전에는 스크린이었는데 outfile 3번을 만들어서 이 파일테이블을 가리키는 거임

프로그램이 이 작업을 해주는 게 아니라 shell이 먼저 이작업을 하고 프로그램을 부름

프로그램은 그것도 모르고 write(1,buffer,n) 를 실행하면 outfile로 출력됨

 

$ prog_name < infile > outfile

input redirection이랑 output redirection을 동시에 한 거임

 

#include <stdlib.h> /* exit */
#include <unistd.h> /* ssize_t */

#define SIZE 512

main()
{
  ssize_t nread;
  char buf[SIZE];
  
  while ( (nread = read (0, buf, SIZE)) > 0)
     write (1, buf, nread);
  exit (0);
}

0(키보드)으로부터 size 512만큼 read해서 buf에 저장 ( default로 open 해줘서 open 따로 안해도 됨 )

키보드에서 입력한 걸(buf에 있는 내용) OS가 1(스크린)에 똑같이 출력해줌

ctrl + D 하면 EOF 역할을 함. 0번 키보드는 EOF가 없으니까 그 역할을 해준다는 말임 

그러면 0이 반환되고 반복문 탈출 가능

 


Standard IO Library

표준 IO 라이브러리는 시스템콜이 아님

시스템콜 위에 라이브러리가 있음 getc, scanf, getline 등 많음 

다 커널에서 read를 사용하는 라이브러리임 

 

IO를 할 때 read, write, open은 기본적으로 함

더 쓰기 편하고 효율적으로 하기 위해서 standard io library를 사용함

사용 안하고 시스템콜을 많이 쓰면 오버헤드가 많고 시간이 많이 들어서 그럼

그리고 프로그래머에게 더 friendly하게 하려고 임

더 effient하고 automatic하게 해줌

 

#include <stdio.h>
#include <stdlib.h>

main()
{
 	FILE *stream;

 	if ( ( stream = fopen ("junk", "r")) == NULL)
 	{
 	   printf ("Could not open file junk\n");
 	   exit (1);
 	}
}

 

fopen을 사용해서 O_RDONLY가 아니라 "r"로 더 쓰기 편하게 함

fopen은 파일디스크립터가 아니라 파일디스크립터의 포인터를 반환함

 

C 표준 라이브러리 (libc.a)가 high level standard IO function들을 다 갖고 있음

fopen, fclose, fread, fwrite, fgets, fputs, fscanf, fprintf


fopen(3)

#include <stdio.h>

FILE *fopen(const char *restrict pathname, const char *restrict type);

		All three return: file pointer if OK, NULL on error

인자로 pathname, type이 들어감

type은 char* 문자열임

 

open에 실패하면 -1을 반환 했는데, fopen에 실패하면 null을 반환함

 


getc(3), putc(3)

#include <stdio.h>

int getc(FILE *istream);

		  Return: next character if OK, EOF on end of file or error


int putc(int c, FILE *ostream); 

					    Return: c if OK, EOF on error   

 

int c;
FILE *istream, *ostream;

/* istream을 읽기전용으로 개방하고, ostream을 쓰기전용으로 개방하라. */
.
.
.
while( ( c=getc(istream)) !=EOF)  //EOF defined in <stdio.h>, -1
       putc(c, ostream);

Buffering

맨 밑에 커널이 있고 위에는 유저인터페이스임

buffer cache는 디스크 같은 block device에 4Kbyte 단위로 IO할 때 사용하는 버퍼임

라이브러리는 유저 메모리 레벨에서 메모리 버퍼메커니즘을 씀

시스템콜을 되도록 적게 부르기 위해서 버퍼 메커니즘을 사용하는 것이 효율적임

메모리에 있는 버퍼를 확보할 때는 malloc을 사용함


fprintf(3)

파일 대신 사용하는 거임

#include <stdio.h> 

int fprintf(FILE *restrict fp, const char   *restrict format, ...); 


    Return: number of characters output if OK, negative value if output error

restrict는 해당 변수와 메모리를 공유하면 안된다는 말임

두 변수가 서로 다른 메모리 공간을 사용해야 한다는 말

#include <stdio.h>
#include <stdlib.h>

int notfound (const char *progname, const char *filename)
{
    fprintf (stderr, "%s: file %s not found\n", progname, filename);
    exit (1);
}

error handling

유닉스에서 error handling할 때 시스템 차원에서 할 수 있게 해줌

커널에서 시스템콜을 실패하면 -1을 반환하고 발생하지 않으면 필요한 값을 반환함

-1을 반환할 때, -1에는 실패한 이유에 대한 정보가 없음

 

실패 이유를 자세히 설명해주는 정보를 갖고 있는 글로별 변수가 있음

errno.h를 include하면 됨

그 헤더 파일에 헥사값으로 다 정의가 되어 있음

 

그 전역변수가 시스템콜을 실행하는 동안 가장 최근의 error type을 저장하고 있음

다른 시스템콜 에러 타입이 overwrite돼서 지워질 수 있어서 에러 발생하면 바로 확인해야함

 

#include <stdio.h>           /* err1.c ― 에러 취급을 감안하여 화일을 개방하라. */
#include <fcntl.h>
#include <errno.h>     //errno is defined

void main(int argc, char** argv)
{
    int fd;

    if ( (fd = open ("nonesuch", O_RDONLY)) == -1)
        fprintf(stderr, "ENOENT: %s\n", strerror(errno));
    
    errno = EACESS;
    perror(argv[0]);
} 

 

strerror 라이브러리 함수가 있음

E로 시작하는 매우 많은 에러들을 #define E 어쩌고 해서 정해놓음

errno를 strerror에 넣으면 그 에러에 대한 설명이 나옴

 

perror는 read only로 존재하지 않는 파일을 open할 때, 파일이 있다면 성공인데 없으면 실패함

없어서 실패하는 경우 말고 owner가 자기가 아니라서 permission 문제로 실패하는 경우가 있음

EACCESS는 access permisson이 없어서 에러, ENOENTR는 파일이 존재하지 않아서 에러

 

#include <stdio.h> // fprintf
#include <fcntl.h> // open
#include <errno.h> //이것만 include하면 errno가 extern으로 지정

main()
{
    int fd;

    fd = open(“nonesuch”, O_RDONLY); //error, errno = ENOENT
    fprintf(stderr, "error %d\n", errno); 
    perror(“first position”);
    fd = open(“existfile”, O_RDONLY); //error, errno = EACESS
    fprintf(stderr, "error %d\n", errno); 
    perror(“second Position”);
} 

-----------------------------------
error 2
First position : There is no such files
error 3
Second position : There is no access permission

 

errno가 자동으로 전역변수로 선언되어 있음

nonesuch라는 파일 이름을 open하려고 할 때 파일이 없다면 -1이 발생하고 

no entry in directory라는 에러넘버가 세팅되고 그 넘버를 출력하면 2가 출력됨

perror 해서 문자열을 first position이라고 하면 그 위치에서 perror가 실행된 것을 알 수 있음

그래서 자동으로 strerror(errno)가 나옴