Files and Directories
파일과 디렉토리
OS는 물리적 디스크를 파일 시스템으로 조직화합니다. => 디렉터리와 파일들이 모여 파일 시스템을 구성
파일 시스템 :
- 파일 시스템은 위치와 이름과 같은 속성들을 가진 파일들의 모음
- 파일의 물리적 위치를 지정하는 대신, 응용 프로그램은 파일명과 오프셋을 지정합니다.(출제가능)
디렉토리 :
- 디렉토리는 디렉토리 항목(directory entries => 파일명과 오프셋을 가진 집합)을 포함한 파일로, 파일명과 파일의 물리적 위치를 디스크 상에서 연결합니다.
- 대부분의 파일 시스템은 디렉터리를 트리 구조로 조직화합니다.
/(루트 디렉터리) 하위에 dirC, dirA(서브(하위) 디렉터리)가 존재 그 하위에는 파일인 my3.dat를 가리킴
같은 이름의 파일이 다른 디렉터리에 존재한다면 존재 가능 -> 위 그림의 my1.dat 파일 예시 참고
=> dirC 디렉터리 하위에는 my3.dat 파일이 존재하므로 파일의 끝 부분을 "leaf node"라고 말함 (말단 노드)
디렉토리
- 루트 디렉토리 (Root directory) : /
- 파일 시스템 트리의 최상위에 위치, 그 아래에 모든 것이 존재함 (자식 or 자손 노드를 포함)
- 상위(부모) 디렉토리 (Parent directory) : .. (현재 작업 중인 디렉토리의 상위(부모) 디렉토리를 나타냄
- 현재 작업 디렉토리 (Current working directory) : . (현재 작업 중인 디렉토리)
- 홈 디렉토리 (Home directory) : 로그인하여 터미널을 열 때, 기본값으로 사용자가 보는 디렉토리 (~) / cd ~ 하면 홈 디렉터리로 돌아감 or cd만 입력하여 홈 디렉터리로 이동이 가능
- 하위 디렉토리 (Sub-directory) : 다른 디렉토리 내부에 속해 있는 디렉토리
경로명 (pathname)
경로명을 표현하는 2가지 방법이 있습니다.
- 절대 경로(Absolute path) 또는 완전 경로(Fully qualified pathname)
- 상대 경로(Relative pathname)
절대 경로 (Absolute path) 또는 완전 경로 (Fully qualified pathname)
- 파일을 지정하기 위해 항상, 슬래시(/)로 시작합니다.
- 예: /dirA/dirB/my1.dat (위 트리 구조 그림 예시)
상대 경로 (Relative pathname)
- 현재 디렉토리에서 시작합니다 (슬래시(/)로 시작하지 않음)
- 예: 현재 작업 디렉토리가 dirA라면, dirC의 my3.dat 파일을 ../dirC/my3.dat로 참조 [현재 작업 디렉터리의 부모(상위) 디렉터리인 루트 디렉터리(/)로 올라가고 dirC 디렉터리 하위에 my3.dat 로 참조하면 됨
경로명 예시
- 현재 작업 디렉토리가 /dirA/dirB일 때, 디렉토리 dirA에 있는 파일 my1.dat을 참조할 수 있는 3가지 방법을 제시하시오.
- /dirA/my1.dat (절대 경로)
- ../my1.dat (상대 경로) : 부모 디렉터리인 dirA로 올라간 후 my1.dat 파일 참조
- ./../my1.dat (상대 경로) : 현재 작업 디렉터리(./)를 표현(/dirA/dirB)[생략 가능]를 하고 부모 디렉터리(../)로 올라 간 후 my1.dat(파일)을 접근 => 어떤 파일의 경로를 상대 경로로 나타낼 때, 그 상대 경로의 가짓수는 무한대(아래 참고!)
=> ../../dirA/my1.dat로 표현 가능 (부모의 부모인 루트 디렉터리로 올라가서 파일의 경로를 지정하도록 표현 가능)
디렉토리 변경 (chdir [change directory] )
#include <unistd.h>
int chdir(const char *path);
현재 작업 디렉토리를 path로 지정된 다른 작업 디렉토리로 변경합니다.
path : 이동하고자 하는 디렉토리 이름
반환 값 (Return) :
- 성공 시 0을 반환
- 실패 시 -1을 반환
chdir() 함수는 현재 프로세스의 작업 디렉토리에만 영향을 미칩니다.
예제 - mychdir.c
#include <unistd.h>
int main (void) {
char *directory = "/tmp";
if (chdir(directory) == -1)
perror("Failed to change current working directory to /tmp");
}
이 예제 코드는 chdir 함수를 사용하여 현재 작업 디렉토리를 /tmp(루트 밑 tmp 폴더)로 변경하려고 시도합니다.
만약 디렉토리 변경이 실패(-1)하면, perror 함수를 통해 "Failed to change current working directory to /tmp"라는 오류 메시지를 출력합니다.
현재 디렉토리 가져오기
#include <unistd.h>
char *getcwd(char *buf, size_t size);
getcwd 함수는 현재 작업 디렉토리의 경로명을 반환합니다. (get current working directory)
- buf : 현재 디렉토리의 경로명을 저장할 사용자 제공 버퍼
- size : buf가 수용할 수 있는 최대 경로명 길이 (문자열 종료 문자 포함)
- 반환 값 (Return) :
- 성공 시, getcwd는 buf에 대한 포인터 값을 반환
- 실패 시, getcwd는 NULL을 반환
버퍼 크기
경로명을 포함하는 문자열을 담을 수 있을 만큼 충분히 큰 버퍼를 항상 제공해야 합니다.
- 파일 크기를 보장하는 한 가지 방법은 PATH_MAX를 사용하는 것입니다.(시스템에서 정의되어 있는 값(PATH_MAX)
- PATH_MAX는 경로명의 최대 길이를 지정하는 선택적 POSIX 상수입니다.
=> getcwd의 버퍼의 값을 char buf[PATH_MAX]; 로 정의 해야 됨 / 하지만, 시스템에 정의가 되어 있지 않은 프로그램은 사용자가 임의로 버퍼의 값을 지정해야 됨 예를 들어, 10이나 20 등으로 지정
#include <limits.h>
#include <stdio.h>
#include <unistd.h>
#ifndef PATH_MAX
#define PATH_MAX 255
#endif
int main(void) {
char mycwd[PATH_MAX];
if (getcwd(mycwd, PATH_MAX) == NULL) {
perror("Failed to get current working directory");
return 1;
}
printf("Current working directory: %s\n", mycwd);
return 0;
}
이 예제 코드는 현재 작업 디렉토리를 가져와 출력하는 프로그램입니다.
- PATH_MAX가 정의되지 않은 경우, PATH_MAX를 255로 정의합니다. => #ifndef(if not defined, 조건식) PATH_MAX로 지정 / 만약, PATH_MAX가 지정이 되지 않을 경우 #define PATH_MAX 255로 정의하여 #endif는 if문의 끝
만약, PATH_MAX 값이 정의 되어 있으면 #define PATH_MAX 255 구문을 건너 뛰고 아래의 코드(int main(void)를 실행
- getcwd 함수를 사용하여 현재 작업 디렉토리를 mycwd 버퍼에 저장합니다.
- 작업 디렉토리를 가져오는 데 실패하면 오류 메시지(perror인 "Failed to get current working directory"를 출력하고 1을 반환하여 프로그램을 종료합니다.
- 성공하면 현재 작업 디렉토리를 출력("Current working diretory: %s\n")을 하고 0을 반환하여 프로그램을 종료합니다.
=> PATH_MAX 값이 255가 나오지 않으면 기존 프로그램에 PATH_MAX값이 저장되어 설정되어 있는 것
검색 경로
- 실행 파일 이름만 주어진 경우, 셸은 PATH 환경 변수에 나열된 모든 가능한 디렉토리에서 해당 실행 파일을 검색(시스템의 환경변수(path)에 등록된 파일을 순서대로 뒤져서 찾습니다. => 리눅스에서 PATH는 홈 디렉토리의 .profile 파일에서 설정할 수 있습니다. (env 명령어로 중간 PATH 환경 변수 밑에 나와 있어 디렉터리 별로 확인가능)
- which 명령어를 입력하여 실행 파일의 완전한 경로명(위치)을 가져오는 데 사용됩니다.
Ex) which ls 를 입력하면 ls의 파일 위치를 알려줌
which 명령어의 역할
- which는 주어진 명령어(프로그램)의 실행 파일이 실제로 어느 경로에 위치해 있는지를 찾아줍니다.
- 예를 들어, which python을 입력하면, 현재 시스템에서 python 실행 파일이 있는 전체 경로를 출력합니다.
- PATH의 시작 부분에 .을 추가하는 것은 위험합니다.
- 만약, .(현재 디렉터리)를 끝이 아닌 맨 처음 부분에 추가하면 위험, 이는 보안 위험으로 간주되며, 셸이 표준 시스템 프로그램 대신 로컬 프로그램을 실행하여 예상치 못한 결과를 초래할 수 있습니다.
디렉토리 접근 (Directory Access)
- 디렉토리를 열고, 닫고, 읽기 위한 세 가지 함수가 제공됩니다.
- opendir, closedir, readdir
파일과 비슷한 맥락으로 디렉터리에 접근하기 위해서는 opendir로 디렉터리를 오픈하고 closedir로 프로그램을 종료합니다. 오픈된 디렉터리 내부에 파일을 1개씩 읽는 readdir로 순서대로 1개씩 반환
디렉토리 열기
opendir 시스템 호출
#include <sys/types.h>
#include <dirent.h>
DIR *opendir (const char *dirname);
- 성공 시: DIR 포인터를 반환
- 실패 시: null 포인터 반환 (파일이 존재 X or 파일을 읽을 권한이 없는 경우 등)
- dirname: 열고자 하는 디렉토리 이름을 의미합니다.
DIR 타입
- dirent.h에서 정의됨
- 디렉토리 스트림을 나타냄
디렉터리 스트림 (을 표현하는 데이터 구조체가 DIR)
- 특정 디렉토리 내 모든 디렉토리 항목의 순서 있는 시퀀스
- 디렉토리 스트림의 항목 순서는 반드시 파일 이름의 알파벳 순서가 아닐 수도 있음
디렉토리 읽기
readdir 시스템 호출
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir (DIR *dirptr);
- 성공 시: 첫 번째 struct dirent 포인터(dirent 구조체의 포인터 값)를 반환
- 실패 시: null 포인터 반환
- dirptr: 읽을 DIR 포인터를 나타냅니다.
설명 :
- 디렉토리를 읽어 dirptr가 가리키는 디렉토리 스트림에서 순차적으로 연속된 항목들을 반환합니다.
- struct dirent: 다음 디렉토리 항목에 대한 정보를 포함하고 있음
- readdir 함수는 디렉토리의 끝을 나타내기 위해 NULL도 반환할 수 있습니다. (디렉터리의 끝에서 readdir 함수를 호출할 때 null을 반환(파일의 끝까지 가서 더이상 읽을 entry가 없을 경우에 null 반환) / null 반환할 때까지 읽음)
dirent의 구조체 필드의 필수 속성 값 -> 시작 : d_ino (Inode number), 마지막: d_name[256] (파일 이름)
디렉토리 닫기와 되감기
closedir 시스템 호출 (파일 사용 후 closedir 호출하여 파일을 닫기)
- 성공 시: 0 반환
- 실패 시: -1 반환
- dirptr: 닫을 디렉토리 경로명을 나타냅니다.
rewinddir 시스템 호출 (내부 포인터를 처음 상태로 되돌리는 역할, rewinddir 후 readdir을 실행하면 처음(첫 번째 디렉터리 엔트리)부터 액세스 함)
- 디렉토리 스트림을 처음 위치로 되돌립니다.
2번째 argument로 argv[1]에 타겟 디렉터리 내용이 들어 있음, opendir함수로 사용자가 주어진 타겟 티렉터리 argv[1]을 open하라고 요청하면 while문을 통하여 readdir 함수를 호출하여 첫 번째 디렉터리 entry의 구조체를 반환 direnp(dirent의 구조체 포인터 타입) 하여 printf함수로 direntp가 가리키는 d_name(파일 이름, 끝부분)을 출력 / 만약, readdir이 더 읽을 게 없을 경우 null을 반환
파일 상태 정보
- lstat와 stat 함수는 파일 이름을 통해 파일에 접근합니다.
- 경로가 심볼릭 링크가 아닐 경우(하드 링크)에도, 두 함수는 동일한 결과를 반환합니다.
- 경로가 심볼릭 링크일 때 :
- lstat 함수는 링크 자체에 대한 정보를 buf에 반환합니다. (lstat은 링크 파일 속성 정보가 반환)
- stat 함수는 링크가 가리키는 원본 파일에 대한 정보(status)를 buf에 반환합니다.
*restrict buf (다루지 않음, just C에서 쓰이는 함수)
stat함수를 사용하여 target 파일의 status 정보를 2번째 파라미터(&statbuf)로 반환을 받아 파일의 last accessed 시간, path, ctime을 문자로 출력 / time_t (long타입)
설명 :
- ctime 함수는 생성된 문자열을 보관하기 위해 정적 저장소를 사용합니다.
- 따라서 ctime을 두 번 호출하면 이전에 저장된 접근 시간 문자열이 덮어쓰여질 가능성이 있습니다.
파일 유형 결정
- struct stat의 st_mode 멤버는 (파일의 접근 권한과 파일 유형)을 지정합니다.
- POSIX는 st_mode 멤버의 파일 유형을 테스트하기 위한 매크로를 제공합니다.
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
int isdirectory(char *path) {
struct stat statbuf;
if (stat(path, &statbuf) == -1)
return 0;
else
return S_ISDIR(statbuf.st_mode);
}
- 이 함수는 char *path라는 파일 경로 문자열을 매개변수로 받습니다.
- struct stat statbuf; 는 stat 구조체 변수를 선언합니다.
- stat 함수는 성공 시 0을 반환하고, 실패 시 -1을 반환합니다.
설명: isdirectory 함수는 주어진 경로가 디렉토리인지 확인합니다. stat 함수가 실패하면 0을 반환하고, 성공하면 S_ISDIR 매크로를 사용해 디렉토리 여부를 판별합니다.이때,디렉터리라면 0이 아닌 값 - "디렉터리" 출력, 디렉터리면 0이 반환됨
일반적인 UNIX 파일 시스템의 구조 (트리 형태)
- 루트 디렉토리 (/) 아래에 여러 주요 디렉토리가 있습니다: (기본값), just 참고만 하고 넘어갈 것
- dev: 장치에 대한 특수 파일
- etc: 시스템 설정 파일
- home: 사용자 디렉토리
- opt: 애플리케이션 패키지
- usr: 공유 가능한 파일
- var: 변동이 있는 파일 (예: 로그 파일)
UNIX 파일 구현
- POSIX는 디스크에서 파일을 나타내는 특정한 방식에 대해 규정하지 않습니다. => just 파일 or 실행파일 정도만 구분
- 그러나 전통적으로 UNIX 파일은 수정된 트리 구조로 구현되었습니다.
- 디렉터리 항목은 파일명과 inode(디렉토리 엔트리 정보)라 불리는 고정 길이의 구조체에 대한 참조를 포함합니다. (2가지 정보를 포함)
inode
- 파일이 생성될 때, 파일에 대한 정보를 포함하는 데이터 구조들이 생성됩니다. =>여기서, 객체는 inode 데이터 구조
- 각 파일에는 고유한 inode가 있으며, 파일 시스템 내에서 inode 번호로 식별됩니다.
- Inodes는 파일에 대한 pointer 정보를 저장(실제 파일 정보 x)합니다. => 파일의 정보는 inode 내부에 저장이 되어 있음 / 이 때문에, stat함수는 inode 번호를 통해서 파일을 지정
- 예를 들어, 사용자 및 그룹 소유권
- 접근 모드(읽기, 쓰기, 실행 권한 정보)
- 파일의 유형과 같은 정보를 포함함
- inode의 수와 크기는 미리 고정되어 있으며, 이는 각 파일 시스템이 보유할 수 있는 최대 파일 수를 나타냅니다.
indirect pointer (3종류) 와 direct pointer가 존재 / single(1단계 거쳐 파일 블록 나타냄), double (2단계 거침), triple (3단계 거침) => direct pointer 값을 추적하면 실제 파일 블록으로 도달할 수 있음!
triple indirect pointer는 double indirect pointer -> single indirect pointer -> direct pointer -> file block (3단계, 나머지 단계 동일)
파일의 크기가 큰 경우, direct pointer만으로 저장할 수 있는 컨텐츠의 크기가 부족하기 때문에 indirect pointer를 두는 것
디렉터리 구현
- 디렉터리는 파일 이름과 파일 위치(디렉터리 엔트리 정보) 간의 대응 관계를 포함하는 파일입니다.
- 디렉터리 = inode 번호(파일의 위치를 가리킴) + 파일 이름
- inode 접근 시
- 프로그램은 경로 이름으로 파일을 참조합니다.
- 운영 체제는 파일 시스템 트리를 탐색하여 적절한 디렉터리에서 파일 이름과 inode 번호를 찾습니다.
- 운영 체제는 inode에 접근하여 파일에 대한 다른 정보를 결정할 수 있습니다.
inode + 파일 이름의 장점
파일 이름을 변경하는 것은 실제 파일의 데이터를 옮기는 것이 아니라, 디렉터리 항목을 수정하여 파일의 경로를 업데이트하는 작업입니다. 이 때문에 파일 이름을 변경하는 작업은 매우 빠르게 수행됩니다.
- 파일 이름을 변경하려면 디렉터리 항목만 변경하면 됩니다.
- 파일은 같은 영역 내에서 디렉터리 항목만(파일의 정보 이동 x, 그래서 빠르게 이동할 수 있는 것) 이동하여 다른 디렉터리로 옮길 수 있습니다.
- 디스크에는 파일의 물리적 복사본이 하나만 존재하면 되며, 파일은 서로 다른 디렉터리에서 여러 이름을 가지거나 동일한 이름을 가질 수 있습니다.
- 디렉터리 항목은 작습니다. 각 파일에 대한 대부분의 정보가 inode에 저장되기 때문입니다.
연습 문제 (inode 포인터)
가정 : inode가 128바이내트이고, 2가지 영역으로 포인터& 상태 정보 공간으로 나누어짐
내부에, 포인터가 4바이트이며, 상태 정보가 68바이트를 차지한다고 가정합니다.
1개의 블록 크기는 8K바이트이고, 블록 포인터는 4바이트(32비트)입니다.
- inode에서 직접 포인터에 할당할 공간은 얼마나 될까요?
- 단일, 이중, 삼중 간접 포인터가 각각 4바이트를 차지합니다 (총 12바이트).
- 128 - 68 - 12 = 48바이트 ( direct pointer가 차지하는 공간의 크기, 12개의 direct 포인터가 존재함, [블록 포인터가 4바이트] )
- 직접 포인터로 표현할 수 있는 파일의 크기는 얼마인가요?
- 8KB = 8 * 2^10 바이트 ( 1GB = 2^(30)바이트, 1MB = 2^(20)바이트, 1KB = 2^(10)바이트 )
- 8 KB * 12 = 96KB
- 단일 간접 포인터는 어떨까요?
- 단일 간접 포인터는 데이터 블록 포인터 2048개를 포함할 수 있는 8K 블록을 참조합니다.
- 2048 * 8192(8KB) = 16,777,216 바이트
- (2 * 2^10, 8KB/4B => ) * (8 * 2^10, 8KB) = (2 * 8) * (2^20) = 16 MB
하드 링크와 심볼릭 링크
링크는 파일 이름과 inode 간의 연관성입니다.
UNIX에는 2 가지 링크 유형이 있습니다: 하드 링크와 심볼릭(소프트) 링크
- 디렉터리 항목은 하드 링크 => 이유는, 파일명을 inode에 직접 연결하기 때문입니다. (파일을 1개 생성하면 1개의 하드링크가 생성됨, 이때, 하드 링크를 추가할 수 있음 (just directory entry를 추가하는 것)
- 심볼릭(소프트) 링크: 간접적으로 연결시키는 링크, 실제 원본 파일이 아닌 별개의 파일(파일 내부에 원본 파일로 가는 pathname(경로명)이 존재 -> os가 심볼릭 링크 내부의 원본 파일의 경로명을 계속 탐색하여 원본 파일을 open (윈도우의 바탕화면 아이콘 역할)
- 각 inode는 해당 inode에 연결된 하드 링크 수(count값)를 포함합니다.
- 파일이 생성될 때, 새로운 디렉터리 항목이 생성되고 새로운 inode가 할당됩니다.
하드 링크
- 하드 링크는 저장 볼륨에 있는 파일에 대한 디렉터리 참조 또는 inode를 찾아갈 수 있는 직접적인 포인터입니다.
- 파일에 연결된 이름은 디렉터리 구조에 저장된 단순한 레이블입니다.
- 동일한 파일에 여러 개의 이름이 연결될 수 있습니다.
- 서로 다른 이름을 통해 접근하더라도, 수정된 내용은 동일한 파일 데이터에 영향을 미칩니다. (서로 다른 파일이 같은 inode를 가리키고 있기 때문, 심볼릭 링크도 동일)
- 하드 링크는 동일한 파일 시스템에 존재하는 데이터만 참조할 수 있습니다.
- 기존 파일을 가리키는 새로운 하드 링크는 새로운 디렉터리 항목을 생성하지만 추가적인 디스크 공간을 할당하지는 않습니다. => 셸 명령어 ln( 시스템 콜 함수 명령어 - link) 함수로 생성할 수 있습니다.
- 새로운 하드 링크(링크 추가)는 inode에서 링크 수를 증가시킵니다. (count값이 0이 되면 inode 자체가 삭제됨, count값이 0보다 큰 값일 경우, inode의 directory entry만 삭제)
- 하드 링크는 쉘 - rm 명령어나 시스템 콜 함수 - unlink 명령어로 시스템 호출로 제거할 수 있습니다. => 이 과정에서 링크 수가 감소합니다.
링크 카운터
- 대부분의 하드 링크를 지원하는 파일 시스템은 링크 카운터라는 참조 카운팅을 사용합니다.
- 각 물리적 데이터 섹션에 정수 값이 저장됩니다. 이 정수는 데이터를 가리키기 위해 생성된 링크의 총 수를 나타냅니다.
- 새로운 링크가 생성되면, 이 값은 1만큼 증가합니다.
- 링크가 제거되면, 이 값은 1만큼 감소합니다.
- 링크 카운트가 0이 되면, 운영 체제는 일반적으로 그 파일을 접근 중인 프로세스가 없으면 파일의 데이터 공간을 자동으로 해제합니다.(inode 삭제)
하드 링크 API
#include <unistd.h>
int link(const char *path1, const char *path2);
int unlink(const char *path);
- link는 path1로 지정된 기존 파일(원본 파일의 경로명)에 대해 path2로 지정된 디렉터리에 새로운 디렉터리 항목(새롭게 생성된 파일의 경로명)을 생성합니다.
- unlink는 path로 지정된 디렉터리 항목을 제거합니다.
- 파일의 링크 수가 0이 되고 파일을 여는 프로세스가 없을 경우, unlink는 해당 파일이 차지한 공간을 해제합니다.
예제
#include <stdio.h>
#include <unistd.h>
if (link("/dirA/name1", "/dirB/name2") == -1)
perror("Failed to make a new link in /dirB");
심볼릭 링크
- 심볼릭 링크는 다른 파일이나 디렉터리의 이름을 포함(원본 파일의 경로명)하는 특수한 유형의 파일입니다.
- 심볼릭 링크의 이름을 참조(탐색)하면 운영 체제는 해당 파일에 저장된 이름을 사용하고, 심볼릭 링크 자체의 이름을 사용하지 않습니다.
- 운영 체제는 파일이 심볼릭 링크라고 판단하면 해당 inode의 데이터 블록에 다른 경로명이 있다고 가정합니다.
- 운영 체제는 그 경로명에 대한 디렉터리 항목을 찾고, 하드 링크와 실제 파일을 만날 때까지 체인을 계속 따라갑니다.
- 심볼릭 링크는 다음 명령어로 생성됩니다: ln -s target link_name
- 심볼릭 링크는 inode의 링크 카운트에 영향을 주지 않습니다.
심볼릭 링크 API
#include <unistd.h>
int symlink(const char *path1, const char *path2);
- path1 : 링크의 내용이 될 문자열을 포함합니다. (원본 파일)
- path2 : 링크의 경로명을 제공합니다. (원본 파일을 나타내는 간접적인 경로명을 포함한 파일)
dirA/name1이 원본파일, dirB/name2 가 새로 만들어진 파일 (추가된 파일)
'Computer Science > 시스템 프로그래밍' 카테고리의 다른 글
시스템 프로그래밍 chapter08. (신호) (0) | 2024.11.07 |
---|---|
시스템 프로그래밍 chapter 06. (UNIX 스페셜 파일) (0) | 2024.11.05 |
시스템 프로그래밍 chapter 04. (4) | 2024.10.09 |
시스템 프로그래밍 chapter 03. (1) | 2024.10.08 |
시스템 프로그래밍 chapter 02. (0) | 2024.10.08 |