leebaek

[UNIX프로그래밍] 4강 - 파일 입출력 본문

수업 정리/UNIX 프로그래밍

[UNIX프로그래밍] 4강 - 파일 입출력

leebaek 2024. 10. 15. 14:29

  • UNIX file 접근 primtives
  • file descriptor
  • open 시스템 호출
  • create 시스템 호출
  • close 시스템 호출
  • read 시스템 호출
  • write 시스템 호출
  • read/write의 효율성
  • lseek와 임의 접근
  • file의 제거
  • 표준 입력, 표준 출력, 표준 오류
  • 표준 입출력의 변경
  • 오류메세지 출력

■ UNIX file 접근 primitivies

□ file : byte들의 linear sequence

□ 파일 입출력

- 저수준 파일 입출력 ( open, close, read, write, dup, dup2, fcntl, lseek, fsync )

- 고수준 파일 입출력 ( fopen, fclose, fread, fwrite, fputs, fgets, fprintf, fscanf, freopen, fseek )

 

> 열린 파일을 참조할 때 

저수준 파일 입출력은 file descriptor 사용

고수준 파일 입출력은 file pointer 사용


■ file descriptor

: 현재 open된 file을 구분할 목적으로 UNIX가 붙여 놓은 번호

 

□  표준 입출력 ( 정수 )

  • 0 : 표준 입력
  • 1 : 표준 출력
  • 2 : 표준 오류 출력

- 한 프로세스가 동시에 open 할 수 있는 file의 개수 제한 -> close 사용


■ open 시스템 호출 ( 기존의 file 열기 or 새로운 file 생성 후 열기 )

□ 파일 열기 ( return 값 file descriptor : 성공 | -1 : 실패 )

 

> 헤더 파일

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *filename, int oflag, [mode_t model]);

int fd1, fd2, fd3;

// 쓰기 전용으로 열 때 ( 파일이 있는 경우 )
fd1 = open("test.txt", O_WRONLY | O_TRUNC );

// 쓰기 전용으로 열 때 ( 파일이 없는 경우 )
fd2 = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC );

// 읽기 + 쓰기 + 추가용으로 열 때
fd3 = open("test.txt", O_RDWR | O_APPEND );

 

flag : 파일의 상태를 조정

 

mode : 파일의 접근 권한 설정 ( 파일을 생성할 때만 사용 )

mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;

// 0600, 0644, 0664 도 가능

■ creat 시스템 호출 ( 기존의 file 열기 or 새로운 file 생성 후 열기 )

□ 파일 열기 ( return 값 file descriptor : 성공 | -1 : 실패 )

- file을 쓰기 가능한 상태로 open ( flag : WONLY 와 같음

- file이 이미 존재하면 mode는 무시 ( 기존 file은 0으로 open )

 

파일 생성 기능이 없던 구 버전 유닉스에서 사용하던 시스템 콜로, 읽기 전용 open()과 같은 의미

 

> 헤더 파일

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *filename, mode_t mode);

■ close 시스템 호출 ( open된 file을 닫을 때 사용 )

□ 파일 닫기 ( return 값 0 : 성공 | -1 : 실패 )

- process 종료 시 open된 file들은 자동으로 close됨

- 동시에 open할 수 있는 file 수 제한 때문에 close 사용

 

> 헤더 파일

#include <unistd.h>
int close(int filedes); // filedes : open된 file의 descriptor

close(fd);

 


■ read 시스템 호출 ( open된 file로 부터 지정 byte수 만큼의 data를 읽어 지정된 저장장소에 저장 )

□ 파일 읽기 ( return 값 실제로 읽은 byte수 : 성공 | -1 : 실패 )

- 처음은 파일의 시작, read() 함수를 실행할 때마다 읽은 크기만큼 오프셋 이동

- return 값 < nbytes : file에 남은 data가 nbytes보다 적을 때 

 

> 헤더 파일

#include <unistd.h>
sszie_t read(int filedes, void *buffer, size_t nbytes); // filedes : open된 file descriptor

 

buffer : 읽은 data를 저장할 곳의 주소

- data type은 상관없음 ( 내가 원하는 타입을 지정 하면 됨 )

 

nbytes :  읽을 byte 수

- 데이터 타입 상관없이 byte 단위로 읽음


■ write시스템 호출 ( 지정된 저장장소에서 지정한 byte수 만큼의 data를 읽어 open된 file에 씀 )

□ 파일 쓰기 ( return 값 쓰여진 byte수 : 성공 | -1 : 실패 )

- return 값 < nbytes : 쓰는 도중 파일에 쓸 공간 없음 : 1 리턴 / 쓰기 전에 꽉차면 -1 리턴

 

> 헤더 파일

#include <unistd.h>
ssize_t write(int filedes, const void *buffer, size_t nbytes);

rfd = open("linux.txt", O_RDONLY);
wfd = open("linux.txt", O_CREATE | O_WRONLY |O_TRUNC, 0644);

while( (n=read(rfd, buf, 6)) > 6 ) {
	if ( write(wfd, buf, n) != n ) perror("Wtire");
}

■ read / write의 효율성 

□ File을 copy하는 프로그램의 실행 시간

  1. BUFSIZE가 512 ( disk blocking factor )의 배수 일 때, 효율적 
  2. system call의 수가 적을수록 효율적

-> 작은 크기의 버퍼를 사용하면 시스템 호출의 빈도가 증가하고, 디스크 접근이 잦아져 성능이 저하됨

-> 너무 큰 버퍼를 사용하면 메모리 낭비와 캐시 효율성 문제가 발생함 )

disk blocking factor : 데이터를 읽고 쓸 때 사용하는 블록 크기 의미


■ lseek 시스템 호출 ( open된 file 내의 특정 위치로 file pointer 이동 )

□ file pointer 이동 ( return 값 이동된 위치(시작점부터의 위치) : 성공 | -1 : 실패 )

 

> 헤더 파일

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);

 

 

 whence (기준점) : whence에서 offset 만큼 떨어진 위치로 이동

  • SEEK_SET : file 시작 지점
  • SEEK_CUR : 현재 file의 위치
  • SEEK_END : file의 끝 지점 ( *마지막 데이터 위치 + 1* )

offset : +/-로 방향 설정 가능

// 실습 문제
int main() {
    char ch = 'X';
    int i, fd;

    fd = open("data1", O_WRONLY|O_CREAT, 0600); // (a)읽기쓰기 가능한 "data1" file을 생성하고, 문자 ‘X'를 10개 씁니다.
    for ( i = 0; i < 10; i++ ){
        write(fd, &ch, 1);
    }

    lseek(fd, 0, SEEK_SET); // (b) "data1" file의 첫 번째 위치로 file pointer를 옮긴 후 문자 ‘Y'를 하나 씁니다.
    write(fd, "Y", 1);

    lseek(fd, 0, SEEK_END); // (c) "data1" file의 마지막 문자 다음 위치로 file pointer를 옮긴 후 문자 ‘Y'를 하나 씁니다.
    write(fd, "Y", 1);

    for ( i = 1; i < 8; i += 2 ) {
        lseek(fd, i, SEEK_SET); // (d) "data1" file의 2, 4, 6, 8번째 문자를 ‘Z'로 바꾸어 씁니다.
        write(fd, "Z", 1);
    }

    lseek(fd, 14, SEEK_SET); // (e) "data1" file의 15번째 위치에 문자 ‘T'를 씁니다.
    write(fd, "T", 1);

    lseek(fd, -2, SEEK_END); // (f) "data1" file의 뒤에서 2번째 위치에 문자 ‘S'를 씁니다.
    write(fd, "S", 1);

    lseek(fd, -2, SEEK_CUR); // (g) 'S' 바로 앞에 문자 ‘W'를 씁니다.
    write(fd, "W", 1);

    return 0;
}

// 결과 : YZXZXZXZXXYWST

■ file의 제거 ( file을 삭제 )

- 헤더 파일이 다름

- file descriptor가 아니라, file name 씀

 

□ unlink ( return 값 0 : 성공 | -1 : 실패 )

- 파일의 hard link를 삭제 -> 다 삭제하면 파일 오픈 불가능 ( free block으로 이동 )

> 헤더 파일

#include <unistd.h>
int unlink(const char *filename);

 

□ remove ( return 값 0 : 성공 | -1 : 실패 )

> 헤더 파일

#include <stdio.h>
int remove(const char *filename)

 

file이 open 되어 있는 상태에서 file을 삭제하면 ?

- 파일을 닫기 전에 파일을 쓸 수 있고, 읽을 수도 있음

-> 파일은 지워졌지만, 파일 테이블 구조체 배열에 파일에 대한 정보(파일 포인터)가 저장되기 때문에 액세스 가능

 

차이점 : unlink는 file만 삭제, remove는 file과 빈 directory도 삭제


■ 표준 입력, 표준 출력, 표준 오류

  • 표준 입력(키보드) : fd = 0
  • 표준 출력(터미널 화면) : fd = 1
  • 표준 오류(터미널 화면): fd = 2

-> 출력 두가지, 2는 주로 테스트할 때 사용


■ 표준 입출력의 변경

redirection :

$ ./a.out < infile ( 입력 )

$ ./a.out > outfile ( 출력 )

$ ./a.out < infile > outfile

 

pipe :

a.out1의 표준 출력이 a.aout2의 표준 입력으로 

$ ./a.out1 | ./a.out2

■ 오류 메시지 출력

perror("error .. ");