상세 컨텐츠

본문 제목

UNIX - standard input, output, error, standard library, error handling

개발/UNIX

by 2021. 9. 30. 02:57

본문

반응형

Redirection

file descriptor 0,1,2 등의 값들은 이미 정의되어있으므로 open하지 않고도 read, write 등의 system call로 호출할 수 있다.

예를 들어 위 그림과 같이 fd0은 입력 장치(키보드) 파일에 1:1로 대응되는 file table을 가리키고 있다.

하지만 다음과 같은 < 명령어를 사용해서 prog_name이라는 프로그램을 실행하면

$ prog_name < infile

아래와 같은 동작을 shell에서 프로그램 시작 전에 실행하게 된다.

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

즉 infile이라는 이름의 파일이 가리키는 file table의 주소를 fd0에 복사해서 대입한다는 의미이므로 아래 그림와 같이 된다.

이 경우에는 fd0에 다른 file의 descriptor를 넣는 것이므로 input redirection이라고 한다. 

 

 

다음은 output redirection 이다.

위와 같이 fd1에 출력 장치 파일에 1:1로 대응된 file table을 가리키고 있지만,

아래와 같은 명령어로 실행하면

$ prog_name > outfile

다음 동작을 shell에서 실행한다.

dup2(outfile, 1);   /* outfile */

outfile이라는 파일의 fd가 가리키는 주소를 fd1에 복사하므로 아래 그림과 같은 상황이 된다.

 

다음과 같이 input / output redirection 두 개를 동시에 할 수 있다.

$ prog_name < infile > outfile

위와 같이 실행한 경우, fd 0에 infile의 descriptor가 들어가고, fd 1에 outfile의 descriptor가 들어간 상태로 실행되므로, fd0로부터 read로 입력시 infile을 입력, fd1로부터 write로 출력시 outfile을 출력한다.

 

example

입출력에 대한 다음 예시 코드를 보자

// io.c
#include <stdlib.h>
#include <unistd.h>

#define SIZE 512

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

fd0으로 read하면 입력장치인 키보드로부터 값을 받아와서 buf에 저장한다. 저장한 값을 write를 통해 fd1로 출력한다. fd1는 출력장치 파일이므로 화면에 출력된다. 결과는 아래와 같다.

$io
This is line 1 // 사용자 입력
This is line 1 // 시스템의 출력
This is line 2 // 사용자 입력
This is line 2 // 시스템의 출력
<Ctrl-D>
$

Ctrl-D를 누르게 되면 읽고 있는 파일이 EOF인 것과 같이 동작하므로 read가 0을 반환하여 루프가 끝나게 된다.

Standard I/O Library

UNIX의 기본적인 system call (read, write, open)을 이용하여 기능이 구축된 라이브러리들에 대해서 알아보자.

UNIX의 기본적인 I/O system call 과 달리 Standard I/O는 부하가 심한 system call을 적게 사용하기 위해 자체적으로 automatic buffering되어 있으며, 프로그래머와의 상호작용에 더 맞추어져 있다. standard library는 사용하기 쉽고, automatic buffering덕에 프로그래머가 효율성에 대해서 고려하지 않아도 된다. 

 

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

main()
{
 	FILE *stream;

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

위 코드를 보면 UNIX의 기본적인 open system call과 달리 library에서 fopen이라는 명령어를 사용한다.

open system call은 파일명과 매개변수로 <fcntl.h>의 flag(O_RDONLY 등)를 넣지만, library의 fopen은 r/w/a 등의 문자를 사용한다. 

 

standard I/O functions에는 다음과 같은 종류들이 있다.

Opening and closing files (fopen and fclose)
Reading and writing bytes (fread and fwrite)
Reading and writing text lines (fgets and fputs)
Formatted reading and writing (fscanf and 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

system call open과 달리 type에 들어가는 arguments의 종류는 다음과 같다.

open system call과 달리 error시 -1이 아닌 NULL을 반환한다.

r or rb        open for reading
w or wb        truncate to 0 length or create for writing
a or ab       append; open for writing at end of file, or   create for writing
r+ or r+b or rb+  open for reading and writing
w+ or w+b or wb+  truncate to 0 length or create for reading   and writing
a+ or a+b or ab+  open or create for reading and writing at   end of file

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

getc는 읽기전용 open, putc는 쓰기전용 open에 해당한다. 다음과 같이 읽어온 데이터를 출력하는 코드를 작성할 수 있다.

int c;
FILE *istream, *ostream;

while( ( c=getc(istream)) !=EOF)  //EOF defined in <stdio.h>, -1
       putc(c, ostream);

 

Buffering

standard I/O는 buffering mechanism을 통해 비효율적으로 system call을 활용하는 것을 방지한다. 

buffering mechanism의 메모리를 확보하기 위해서는 malloc을 통해 할당한다.

Writing error message with fprintf

#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는 서로의 변수가 메모리를 공유하면 안된다는 뜻이다.

example

예제 코드는 다음과 같다.

#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

system call 에서 에러가 발생하면 -1을 반환하고 에러가 없다면 0을 반환한다. 

에러의 종류는 다양한데 어떠한 에러가 발생했는지에 대한 정보가 <errno.h>헤더의 errno에 'E'로 시작하는 상수값으로 정의되어있다. 

errno는 system call에서 가장 최근에 발생한 error type을 저장하고 있다.

errno는 전역으로 접근 가능한 정수형 변수이다. 

error 발생 후 errno에 저장된 error값은 새로운 system call이 정상적으로 수행되었을 때, 그 값은 리셋되지 않는다.

따라서 system call에서 에러가 발생하는 즉시 errno를 확인해야한다. 

#include <string.h> 

char *strerror(int errnum);

strerror 함수는 errno의 값을 매개변수로 대입하였을 때, 해당 error를 설명하는 string값을 반환한다. 

#include <stdio.h>

void perror(const char *msg);

 다음 예시 코드를 보자

#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]);
}

nonesuch라는 없는 파일을 readonly로 open하려는 상황에서 파일이 없는 경우에도 에러가 발생하여 -1을 반환하지만, 또 nonesuch라는 파일의 읽기 권한이 user에만 있고 접근하는 group이나 other에서는 권한이 없을때도 발생한다. 이와같이 에러발생의 원인 여러가지 중 어떤 원인인지를 구분해야한다.

 

에러가 발생하였을 때 errno에 에러 값이 저장되고 이를 strerror를 통해 문자열로 반환한다. 

main함수를 실행할 때의 매개변수 argc는 프로그램을 실행할 때 전달한 매개변수의 개수, 각각의 매개변수들이 담긴 배열이 argv이다. argv[0]에는 실행하는 프로그램 파일의 절대경로(이름)가 담기고, argv[1]부터 뒤로 매개변수들의 값이 담긴다. 

perror의 매개변수로 전달되는 argv[0] 프로그램 이름을 출력하고, 그 다음으로 strerror(errno)의 기능을 한다. 위 코드에서는 perror를 실행하기 전 errno에 EACESS라는 접근권환 관련 에러 상수를 넣었기 때문에 출력결과는 다음과 같이 나온다.

ENOENT: No such file or directory // strerror
a.out: Permission denied // perror

 

 

또 다른 예시 코드를 보자

#include <stdio.h>
#include <fcntl.h>
#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

error 2, ENOENT는 해당 경로에 "nonesuch" 파일이 없어서 생기는 에러이고, error 3, EACESS는 "existfile"에 대한 접근 권한이 없어서 생기는 에러이다.

반응형

관련글 더보기