리눅스의 파일에 대해 파보자!

funky·2023년 10월 23일
0
post-thumbnail
post-custom-banner
이 글은 <시스템 프로그래밍 리눅스 & 유닉스(이종원 지음)> 책을 공부하며 정리한 것입니다!

파일의 구성요소에 대해 알아보자!

리눅스에서 파일은

  • 파일명
  • inode
  • 데이터 블록
    으로 구성된다.

하나씩 파보자 !


1. 파일명

파일명은 사용자가 파일에 접근할 때 사용하며, 파일명과 관련된 inode가 반드시 있어야 한다.

< 파일명과 관련한 여러 사항들 >
1. 파일명이나 디렉터리명으로 /(디렉터리 구분)와 Null(경로 이름의 끝을 나타냄)을 사용할 수 없음
2. 혼동을 줄 수 있는 특수문자는 사용 자제
3. 알파벳은 대소문자를 구분함
4. 파일명과 디렉터리명이 '.'으로 시작하면 '숨김파일'로 간주

2. inode

inode는 파일에 대한 정보를 저장하고 있는 객체로 실제 디스크에 저장되어있다.

  • 외부적으로는 번호로 표현하며, 내부적으로는 두 부분으로 나누어 정보를 저장한다.

inode의 첫 번째 부분에는 파일에 관한 정보가 저장되는데,

  • 파일 종류
  • 파일 접근 권한
  • 파일 크기
  • 소유자
  • 소유 그룹
  • 파일 변경 시각
  • 하드 링크 수
  • 데이터 블록 수
    등이 있다 !

ls -l 명령으로 확인 가능


inode의 두 번째 부분에는 파일의 실제 데이터가 저장되어 있는 데이터 블록의 위치를 나타내는 주소가 저장된다 !

ls -i 명령으로 확인 가능


3. 데이터블록

데이터블록은 실제로 데이터가 저장되는 하드 디스크의 공간

  • 일반 파일이나 디렉터리, 심벌릭 링크는 데이터 블록에 관한 내용을 직접 저장하지만, 장치 파일은 데이터 블록을 사용하지 않고 장치에 관한 정보를 inode에 저장한다 !


파일의 종류와 특징을 알아보자!

리눅스에서 파일은 크게 세 종류가 있다.

  • 일반 파일
  • 특수 파일
  • 디렉터리



파일의 종류가 궁금하다면

ls -l 명령을 사용하자!
명령의 결과로 나온 첫 글자가 장치의 종류를 의미한다.

- : 일판 파일
d : 디렉터리
b : 블록 장치 특수 파일
c : 문자 장치 특수 파일
l : 심벌릭 링크


자 그럼, 하나씩 파보자!


1. 일반 파일

리눅스에서 사용하는 대부분의 파일 이 여기에 해당한다.
대부분의 파일이라고 한다면 ,

  • 텍스트 파일
  • 실행 파일
  • 라이브러리
  • 이미지
    등을 말하며, 데이터 블록에 텍스트나 바이너리 형태의 데이터를 저장하고 있다!

2. 특수 파일

리눅스에서 통신을 하거나 터미널, 디스크 등의 장치를 사용할 때는 특수 파일을 사용해야함.
장치 관련 특수 파일을 장치 파일이라고 하는데,
이 장치 파일은 데이터 블록을 사용하지 않고 장치 번호를 inode에 저장한다.


3. 디렉터리

리눅스에서는 디렉터리도 파일로 취급한다!

디렉터리와 연관된 데이터 블록은 해당 디렉터리에 속한 파일의 목록과 inode를 저장한다.

디렉터리를 생성하려면 mkdir, 삭제하려면 rmdir 혹은 rm -r, 복사하려면 cp -r 명령을 사용한다.



파일을 생성하고 삭제해보자 !

디렉터리를 생성할 때는 mkdir() 함수를 사용하고 삭제할 때는 rmdir() 함수를
사용한다

추가로, 이 함수들은 디렉터리를 생성화고 삭제할 때 사용하는 명령과 이름이 같아서
man -s 2 이렇게 검색해야한다.

저기서 2가 뜻하는 것은 시스템 호출 섹션의 mkdir, rmdir을 찾겠다는 것을 의미하며
섹션 1은 리눅스에서 사용하는 일반적인 명령, 섹션 3라이브러리 함수를 의미한다


mkdir() : 디렉터리 생성

  • 생성하려는 디렉터리명을 포함한 경로를 받아 생성하는 디렉터리의 기본 접근 권한을 지정한다.
  • 수행 성공 시 0을, 실패 시 -1를 리턴한다.

함수 원형: int mkdir(const char *pathname, mode_t mode);


  • 접근 권한을 755로 지정해 han 디렉터리를 생성하는 코드
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
	if(mkdir("han", 0755) == -1) {
    	perror("han");
        exit(1);
    }
}

rmdir() : 디렉터리 삭제

  • 삭제하려는 디렉터리명을 포함한 경로를 인자로 받아 디렉터리를 삭제
  • 수행 성공 시 0을, 실패 시 -1를 리턴한다.

함수 원형: int rmdir(const char *pathname);


  • 코드
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
	if(rmdir("han") == -1) {
    	perror("han");
        exit(1);
    }
}

디렉터리를 관리해보자!

1. 현재 작업 디렉터리 위치 검색

현재 작업 디렉터리의 위치를 알아낼 때 사용하는 함수는

  • getcwd()
  • get_current_dir_name()
  • getwd()

요렇게 세 개이다.

cf. 현재 디렉터리의 위치를 알려주는 명령은 pwd

getcwd() 예제

  • getcwd() 는 현재 디렉터리의 절대 경로를 리턴한다.

함수 원형: char *getcwd(char *buf, size_t size);

  • buf는 현재 디렉터리의 절대 경로를 저장할 버퍼 주소
  • size 는 버퍼의 크기

getcwd()에서 인자를 지정하는 방법은 세 가지정도가 있는데,

    1. buf에 경로를 저장할 만큼 충분한 메모리를 할당, size에 그 크기 지정
      -> 사용자가 지정한 버퍼에 경로 지정
    1. buf에 null, size에 할당이 필요한 메모리 크기 지정
      -> size에 지정한 크기로 버퍼를 할당하고 버퍼에 경로 저장
    1. buf에 null, size에 0으로 지정
      -> 저장할 경로의 크기에 맞게 시스템이 알아서 버퍼를 할당하고 경로 저장
  • 2와 3의 경우
    시스템 내부에서 malloc()함수로 버퍼가 할당되므로 사용 후 free() 함수로 메모리를 해제해야함.
  • getcwd()에서 저장할 경로가 버퍼의 크기를 넘으면 NULL을 리턴한다!

  • 코드
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
	char *cwd;
    char wd1[BUFSIZ];
    char wd2[10];
    
    getcwd(wd1, BUFSIZ);
    printf("wd1 = %s\n", wd1);
    
    cwd = getcwd(NULL, BUFSIZ);
    printf("cwd1 = %s\n", cwd);
    free(cwd);
    
    cwd = getcwd(NULL, 0);
    printf("cwd2 = %s\n", cwd);
    free(cwd);
    
    if(getcwd(wd2,10) == NULL){ 
    	perror("getcwd"); // Numerical result out of range
        exit(1);
    }
}

get_current_dir_name() 예제

  • get_current_dir_name() 는 현재 디렉터리의 절대 경로를 리턴한다.

함수 원형: char *get_current_dir_name(void);

인자로 아무것도 전달하지 않으며,
시스템이 메모리를 자동으로 할당해 경로를 저장하고 리턴한다.

  • getcwd(NULL, 0)과 같은 방식으로 동작한다고 생각하면 됨!
  • get_current_dir_name()을 사용하려면 _GNU_SOURCE 를 먼저 정의해야함

  • 코드
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
	char *cwd;
    
    cwd = get_current_dir_name();
    printf("cwd = %s\n", cwd);
    free(cwd);
}

2. 디렉터리명 변경 : rename()

  • rename() 는 디렉터리 명을 변경할 때 사용한다.

함수 원형: int rename(const *oldpath, const char *newpath);

  • 두 번째 인자로 지정한 이름이 이미 있다면 해당 디렉터리를 지움
  • 실행 도중 오류가 발생하면 원본과 새로운 디렉터리명이 모두 남는다.
  • 수행에 성공하면 0을 실패하면 -1을 리턴한다

  • 코드
#include <sys/path.h>
#include <stdlib.h>
#include <stdio.h>

int main(){
	if (rename("han", "bit") == -1){
    	perror("rename");
        exit(1);
    }
}

3. 디렉터리 이동

  • 프로그램에서 디렉터리를 이동할 때는 chdir()와 fchdir()을 사용

chdir()

함수 원형: int chdir(const *path);

  • 이동하려는 디렉터리 경로를 인자로 받음
  • 절대경로와 상대경로 모두 사용 가능!
  • 수행에 성공하면 0을 실패하면 -1을 리턴

  • 코드
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
	char *cwd;
    
    cwd = getcwd(NULL, BUFSIZ);
    printf("1. current dir: %s\n", cwd); //    /home/jw/src/ch2
    
    chdir("bit");
    
    cwd = getcwd(NULL, BUFSIZ);
    printf("2. current dir: %s\n", cwd); //  /home/jw/src/ch2/bit
    
    free(cwd);
}
  • 디렉터리 이동은 프로그램 내부에서만 진행된 것으로 pwd 명령으로 확인하면 현재 디렉터리가 바뀐 것은 아님을 알 수 있다.(여전히 /home/jw/src/ch2)

fchdir()

함수 원형: int fchdir(int fd);

  • 파일 디스크립터를 인자로 받음
  • fchdir()를 사용하려면 open()로 해당 디렉터리를 먼저 열어야 함.
  • 수행에 성공하면 0을, 수행에 실패하면 -1을 리턴한다.

  • 코드
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(){
	char *cwd;
    int fd;
    
    cwd = getcwd(NULL, BUFSIZ);
    printf("1. current dir: %s\n", cwd); //    /home/jw/src/ch2
    
    fd = open("bit", O_RDONLY);
    fhdir(fd);
    
    cwd = getcwd(NULL, BUFSIZ);
    printf("2. current dir: %s\n", cwd); //  /home/jw/src/ch2/bit
    
    close(fd);
    free(cwd);
}
  • 디렉터리 이동은 프로그램 내부에서만 진행된 것으로 pwd 명령으로 확인하면 현재 디렉터리가 바뀐 것은 아님을 알 수 있다.(여전히 /home/jw/src/ch2)

4. 디렉터리 내용 읽기

디렉터리 열고 닫기, 내용 읽기, 오프셋 이동하는 함수에 대해 알아보자!


opendir()

함수 원형: DIR *opendir(const char *name);

  • 인자로 지정된 디렉터리를 열고 해당 디렉터리에 대한 디렉터리 스트림을 생성
  • 열기에 성공하면 스트림의 포인터를, 실패하면 NULL을 리턴한다!

closedir()

함수 원형: int closedir(DIR *dirp);

  • 인자로 지정한 DIR 객체가 가리키는 디렉터리를 닫는다.
  • 닫기에 성공하면 0을, 실패하면 -1을 리턴한다!

readdir()

함수 원형: struct dirent *readdir(DIR *drip);

  • 인자로 지정한 DIR 객체가 가리키는 디렉터리의 내용을 한번에 하나씩 읽는다

  • 디렉터리 내용을 차례로 읽고 더 이상 읽을 것이 없으면 NULL을 리턴

  • readdir()는 디렉터리에 있는 항목의 정보를 가리키는 dirent 구조체의 포인터를 리턴

struct dirent {
	ino_t 		    	d_ino;   //해당 항목의 inode 번호
    off_t 		 		d_off; 	// 디렉터리 오프셋의 위치
    unsigned short 		d_reclen; 	//해당 항목의 레코드 길이
    unsigned char 		d_type; 	//파일의 종류
    char 				d_name[256]; //항목의 이름 
};

- 코드
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main(){
	DIR *dp;
    struct dirent *dent;
    
    dp = opendir(".");
    
    while ((dent = readdir(dp))) {
    	printf("Name = %s ", dent->d_name);
        printf("Inode: %d\n", (int)dent->d_ino);
    }
}

closedir(dp);

telldir/seekdir/resinddir()

  • 디렉터리를 열고 내용을 읽으면 디렉터리 스트림에서 현재 내용을 읽고 있는 위치를 나타내는 오프셋 값이 이동
  • 오프셋의 현재 위치를 읽고 이동시키는 함수로 telldir(), seekdir(), rewinddir()
#include <sys/types.h>
#include <dirent.h>

long telldir(DIR *dirp);
void seekdir(DIR *dirp, long loc);
void rewinddir(DIR *dirp);
  • telldir()
    : 인자가 가리키는 디렉터리 스트림에서 현재 위치 리턴
    : 오류가 발생하면 -1 리턴

  • seekdir()
    : 디렉터리 스트림에서 readdir() 함수가 다음 항목을 읽을 수 있는 위치로 오프셋을 이동시킨다.
    : loc에 지정하는 값은 telldir()함수가 리턴한 값이어야 한다.
    : 아무것도 리턴하지 않음

  • rewinddir()
    : 디렉터리 스트림의 위치를 디렉터리 시작 지점으로 이동시킴
    : 아무것도 리턴하지 않음

profile
living Fun & Lucky
post-custom-banner

0개의 댓글