파일 시스템

  • 파일(file) 종류

    • 보통 파일(regular file or normal file): 가장 일반적인 것으로, 데이터가 들어 있는 파일

    • 디렉터리(directory): 보통 파일들을 담아 둘 수 있는 파일

    • 심볼릭 링크(symbolic link or soft link): 다른 파일을 참조만 하는 파일로, 윈도우의 바로가기 개념과 유사하다. 심볼릭 링크 파일을 열면 커널이 자동으로 연결된 파일을 열어 준다. 다른 파일에 대한 별명과 같은 개념이다. 반대로 하드 링크(hard link)도 존재하는데, 이는 완전한 복사를 의미하므로 원본을 지워도 링크로 파일을 열 수 있지만, 심볼릭 링크는 원본을 지우면 파일을 열 수 없다. >> 명령어: ln -s [원본 파일 경로] [링크 파일 경로]

    • 디바이스 파일(device file): 디바이스(하드웨어)를 파일로 표현한 것으로, 커널은 하드웨어와 소통하기 위해 디바이스 드라이버를 이용해서 디바이스 파일에 접근한다. 예를 들어, df -h를 이용해서 디스크 정보를 출력해보면 파일 시스템 필드에 디바이스 파일 이름이 보일 것이다. /dev/sda 는 첫 번째 SSD 또는 HDD를 의미한다. 이 파일에 접근하면 SDD나 HDD에 기록된 데이터를 조작할 수 있다.

      디바이스 파일은 다루는 하드웨어에 따라 문자 디바이스 파일(character device file)블록 디바이스 파일(block device file)이 있다. 차이점은 원하는 시점에 원하는 곳에 접근할 수 있는지다. 예를 들어, SSD와 HDD는 대표적인 블록 디바이스다. 그리고 프린터나 모뎀은 문자 디바이스다.

      그러나 디바이스 파일 중에 /dev/null 의 경우 내용이 비어 있고 무엇을 써도 공중으로 사라져버린다. 이는 윈도우에서 휴지통의 개념과 유사한데, 리눅스 쉘에서 표준 입출력 및 오류를 보고 싶지 않을 때 /dev/null 로 출력해서 없앨 수 있다. 예를 들어보면, "ls -l > /dev/null" 을 쉘에 입력하면 현재 파일의 목록들은 뜨지 않는다. 파일 설명자를 이용해서 특정 방향을 지정해서 무시할 수도 있다.
      "[커맨드] 2 > /dev/null" 과 같이 입력하면 표준 오류 출력만 무시한다. 참고로 0은 표준 입력, 1은 표준 출력, 2는 표준 오류 출력이다.
      "[커맨드] > /dev/null 2&1" 을 입력하면 표준 출력, 오류 출력 둘 다 무시한다는 의미이다.
      "[커맨드1] > [커맨드2]" 의미는 커맨드1의 쉘 명령어의 결과를 커맨드2로 전달하는 것을 의미한다. "<" 도 있다. 참고로 ">>" 는 커맨드2가 파일일 경우 새로 쓰는 것이 아니라 내용을 추가하는 것을 의미한다.

      이처럼 실제 물리적 디바이스가 없는 파일로는 /dev/random 이 있다. 이 파일은 랜덤 값을 추출할 때 참조하는 파일이다.

    • 명명된 파이프(named pipe): 프로세스 간 통신(IPC)에 사용하는 파일이다. FIFO 라고도 불린다.

  • 파일의 메타 정보: 파일 유형, 권한, 크기, 마지막 수정 시간 등이 있다.

    • "ls -l" 을 쳐보면 다음과 같은 결과를 얻는다. 7개의 항목을 볼 수 있다.

      • drwx------+ 3 ke2ek staff 96 Oct 23 21:15 Desktop

      • "drwx------+" 에서 첫 글자 d는 디렉터리임을 나타내는 파일 종류이고, 그 다음부터는 파일의 권한을 의미한다. 소유자만 읽기/쓰기/실행 권한만 가지고 있음을 알 수 있다. (r: 읽기, w: 쓰기: x: 실행)

      • "3" 하드 링크 수

      • "ke2ek" 소유자 이름

      • "staff" 그룹

      • "96" 파일 크기

      • "Oct 23 21:15" 갱신 시각

      • "Desktop" 파일명

  • 파일 시스템과 마운트

    • 리눅스에서는 파일 시스템 모두 트리 구조로 관리가 되며, 보통 SSD나 HDD, USB 메모리처럼 물리적인 기억 장치에 파일 시스템이 존재하는데, USB 같은 외부 장치는 마운트(mount)하여 파일 시스템에 연결할 수 있다.
      "mount -t ext4" 명령어로 현재 사용중인 파일 시스템을 확인할 수 있다. 아마 파티션 개수만큼 나올 것이다. ext4는 현재 리눅스에서 가장 일반적으로 사용되는 파일 시스템이다.
    • 마운트된 모든 파티션 보기: mount
    • 마운트 하기: mount [디바이스 파일] [마운트 할 경로]
    • 마운트 해제: umount [디바이스 파일] 또는 umount [마운트 지점]
    • 특정 옵션을 주어 파티션 다시 마운트: mount -o remount,rw [디바이스 파일]

메모리 관리

  • 가상 메모리(Virtual Memory)는 제한적인 물리 메모리를 프로세스가 모두 사용할 수 있도록 프로세스마다 독립적으로 물리 메모리를 사용하고 있음을 추상화한 것이다. 즉, 메모리를 필요로 하는 프로세스 사이에서 물리 메모리를 공유하도록 하여, 시스템이 실제 가진 것보다 더 많은 메모리를 가진 것처럼 보이게 한다.
  • 메모리 관리 서브 시스템
    • 넓은 주소 공간: 가상 메모리는 실제 물리 메모리보다 몇 배나 더 클 수 있다.
    • 보호: 시스템의 각 프로세스는 각자의 독립된 주소 공간을 갖는다. 가상 주소공간은 서로 완벽하게 분리되어 있어 어떤 응용 프로그램을 실행하는 프로세스는 다른 프로세스에 영향을 줄 수 없다.
    • 메모리 매핑: 실행 이미지와 데이터 파일을 프로세스의 주소공간에 매핑하기 위해 사용된다. 메모리 매핑에서 파일의 내용은 프로세스의 가상 주소공간에 직접 연결된다.
    • 공정한 물리적 메모리 할당: 메모리 관리 서브시스템은 시스템에서 실행중인 프로세스들이 서로 공정하게 물리적 메모리를 공유할 수 있게 한다.
    • 공유 가상 메모리: 프로세스들이 메모리를 공유해야 하는 순간이 있다. 각 프로세스의 가상 주소공간에 복사본을 각각 가지는 것이 아니라 물리적 공간에 하나의 복사본을 가지고 프로세스의 가상 주소가 해당 물리 주소를 참조하도록 한다. 리눅스의 동적 라이브러리는 여러 프로세스가 실행 코드를 공유하는 대표적인 예이다.
  • 요구 페이징(Demand Paging): 실제 가상 메모리보다 훨씬 적은 물리 메모리만 있기 때문에, 물리 메모리가 비효율적으로 사용되지 않도록 해야 한다. 이를 위해 실행 중인 프로그램이 현재 사용하는 부분만 물리 메모리에 올리는 방법이 있다. 즉, 실행 시 접근되는 부분만 메모리에 읽어들이는 기법이다.
  • 스와핑(Swapping): 비어 있는 물리 메모리 공간이 없다면, 기존의 것들과 교체해야 한다. 여기서 기존에 올라간 메모리에 값이 쓰이지 않았다면 그대로 제거하면 되지만, 값이 쓰였다면 나중에 다시 사용될 수 있도록 해당 공간(=더티 페이지=dirty page)을 저장해놔야 한다. 이를 메모리에서 제거할 때 스왑 파일(swap file)이라는 특별한 파일에 저장한다. 참고로 스왑 파일에 접근하는 것은 프로세서나 물리 메모리에 접근하는 것보다 시간이 오래 걸린다. 리눅스에서는 제거될 페이지를 공정하게 선택하기 위해 가장 최근에 사용된 페이지 수명 기법(LRU Algorithm)을 사용한다.
  • 페이지 테이블(Page Table): 가상 주소공간에서는 페이지(page) 단위로 메모리를 관리한다. 보통 4KB이다. 예를 들어, 어떤 프로세스가 10KB 메모리가 필요하다면 페이지 2개하고 2KB가 아니라 페이지 3개를 할당해야 한다. 여기서 내부 단편화가 발생한다. 물리 주소공간에서는 프레임(frame) 단위로 메모리를 관리한다. 가상 주소로 실제 물리 주소를 알아내기 위해 페이지 번호페이지 내 오프셋을 이용해서 페이지 테이블을 참조한다. 페이지 번호로 프레임 번호를 알아낸 뒤, 프레임 내에서 오프셋을 적용하면 실제 물리 메모리 주소를 알아낼 수 있다. 페이지 테이블은 크기가 매우 커서 이를 효율적으로 저장하고 관리하는 방법들이 많이 연구되었으며, 리눅스는 3단계 페이지 테이블을 가정한다.
  • 메모리 매핑(Memory Mapping): 주소 변환을 하기 전에 실행 이미지를 프로세스의 가상 주소공간으로 가져와야 한다. 실행 이미지가 링크해서 사용하는 공유 라이브러리도 마찬가지다. 리눅스는 실행파일을 실제 물리적 메모리에 가져오는 대신, 단지 프로세스의 가상 메모리와 연결만 시킨다. 그리고 응용 프로그램이 실행되면서 프로그램의 일부가 참조됨에 따라, 실행 이미지로부터 해당하는 이미지 부분을 메모리로 가져온다.(by Demand Paging) 이렇게 이미지를 가상 주소공간으로 연결하는 것을 메모리 매핑이라 한다.
    • 프로세스의 메모리 맵 보기: cat /proc/<PID>/maps
    • 현재 실행중인 프로세스의 메모리 맵 보기: cat /proc/self

프로세스

  • 프로세스실행 중인 프로그램을 의미하고, 프로그램이란 파일 형태로 존재하는 실행 가능한 파일을 의미한다. 즉, 하나의 프로그램으로 새로운 프로세스를 계속 만들 수 있다.

  • 프로세스 목록 보기: ps -ef

    • UID PID PPID C STIME TTY TIME CMD
      501 478 477 0 6:20AM ttys004 0:00.31 -zsh

    • 각 프로세스는 식별자로 프로세스 ID를 가진다. 예를 들어, 프로세스를 강제로 종료하려면 "kill -9 [PID]"를 입력한다.

    • 또한, 프로세스는 부모-자식 관계를 가진다. 부모는 자식 프로세스를 생성하고 종료할 때까지 기다리거나 등 의존적이게 해동할 수 있다.

 

'Programming Language > C in Linux System' 카테고리의 다른 글

커맨드라인 인자, GCC 컴파일  (0) 2020.11.02

커맨드라인 인자

  • argc: 인자의 개수 (최소 1)
  • argv: 각 인자의 값
  • 바이너리 파일 이름은 첫번째 인자에 해당됨.
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
	int i;
	printf("argc=%d\n", argc);
	for (i = 0; i < argc; i++) {
		printf("argv[%d]=%s\n", i, argv[i]);
	}
	exit(0);
}
  • 실행 결과
    • 쉘 특성 상 문자열을 큰 따옴표(",")로 묶어서 인자로 넘기면 공백도 포함됨.
    • 쉘 특성 상 와일드 카드 사용 시 현재 경로 기준으로 매칭되는 파일 이름을 인자로 넘김.
$ ./args
argc=1
argv[0] = ./args
$ ./args x
argc=2
argv[0]=./args
argv[1]=x
$ ./args x y
argc=3
argv[0]=./args
argv[1]=x
argv[2]=y
$ ./args x y z
argc=4
argv[0]=./args
argv[1]=x
argv[2]=y
argv[3]=z
$ ./args "x y z"
argc=2
argv[0]=./args
argv[1]=x y z


$ ./args *.c
argc=3
argv[0]=./args
argv[1]=hello.c
argv[2]=args.c
$ ./args "*.c"
argc=2
argv[0]=./args
argv[1]=*.c

 

GCC 컴파일러

gcc -g -W -Wall -o [실행파일] [C파일]
  • -o 는 목적 파일 이름 주는 옵션
  • -Wall 은 소스 수준의 정보를 디버깅 시 이용하기 위한 옵션.
  • 위 두 개는 항상 옵션으로 줄 것
  • 참고
 

gcc와 make 강좌: gcc 강좌

디렉토리명은 -I 라는 문자 바로 다음에 붙여서 씁니다. 즉 -I <디렉토리>라는 식이 아니라 -I<디렉토리> 입니다. 또한 유닉스에 있어 표준 헤더 화일 디렉토리는 /usr/include 라는 사실을 기억하시기

doc.kldp.org

man 명령어

리눅스 라이브러리 함수의 정보가 필요한 경우 매우 유용한 명령어

man strlen

 

+ Recent posts