컴퓨터 구조 요약

- CPU - RAM, HDD/SSD, IO 장치

 

멀티 태스크와 가상 CPU

- 한 순간에 CPU는 하나의 프로세서만 처리 가능. 시간을 잘게 나누어 여러 프로세스를 돌아가며 처리하여 다중 작업 수행

- 가상 CPU : 멀티 태스크 작업을 위해 하나의 CPU를 시간을 나눠 스케줄링을 통해 여러 작업을 동시 수행하는 것

 

가상 메모리 : 부족한 1차 메모리 공간을 보완하고, 프로세서로부터 OS를 보호하기 위한 방법.

- 각 프로세서는 2^32 = 4gb 정도의 가상 메모리 공간을 가짐, 4kb 고정 크기 페이지로 나뉘어짐

- 각 가상 메모리는 코드 영역(프로그램), 데이터 영역(정적변수), 힙(동적할당), 스택(매개변수, 지역변수) 영역으로  구분  

- 프로세서는 메모리 관리 유닛 MMU/운영체제에 의해 가상 메모리 주소가 1차+ 2차 메모리 물리 메모리 주소다 맵핑

   -> 실제 물리주소는 OS가 관리하여 프로세서가 타 프로세서의 공간을 읽고쓰기는 것을 방지 => OS 보호

- 각 프로세서는 큰 가상 메모리 공간을 가져도 실제 사용하는 곳만 1차 메모리다가 할당. 쓰지 않는 건 2차 메모리서 보관

- page in(swap in), page out(swap out) : 2차 메모리에 있는 걸 1차메모리로 옮겨와 사용(page in), 반대의 경우 (page out)

- IPC 프로세서간 통신 : OS가 두 ps간 한 가상 주소를 같은 물리 주소로 맵핑시 ps끼리 메모리 공유 = 서로RW가능 = 통신

- memory mapped file : 파일을 메모리에 맵핑 -> 메모리 RW = 파일 RW (시스템 콜 : mmap)

 *nand2tetris 에선 memory mapped io , io장치 입출력을 메모리에 맵핑하고 그 매모리 값을 읽고쓰면서 제어함.

 

메모리 API

#include <stdlib.h>

- void *malloc(size_t size); : size만큼 동적 메모리 공간(힙) 할당. 시작 주소 void *로 반환

- void *calloc(size_t nmemb, size_t size); : nmemb x size 를 힙에 할당, malloc과 차이는 초기값 0 설정

- void *realloc(void *ptr, size_t size) : malloc or calloc으로 할당된 공간을 축소하거나 늘림.

- void free(void *ptr); : 동적할당 해제

 

빌드 과정

- 전처리 : 헤더 파일 인클루드, 매크로 처리 

- 컴파일 : 소스 코드 -> 어셈블리 파일

- 어셈블 : 어셈블리 파일 -> 오브젝트 바이너리(ELF, a.out <- aseember output)

- 링킹 : 오브젝트 바이너리와 필요한 라이브러리 연결하여 최종 실행파일 or 라이브러리(*.a or *.so) 만듬

 

라이브러리

- 정적 라이브러리 : 컴파일시 포함, *.a(리눅스)/*.lib(윈도우) 파일

- 공유/ 동적 라이브러리 : 빌드 시 체크, 링크 로더가 런타임 시작 시 포함. *.so(리눅스), *.dll(윈도우)

 -> 보통 동적 링크 사용

- file 명령어 : 어떤 플랫폼 실행파일인지, 어떻게 링크 됬는지 확인 가능

- ldd 명령어 : 어떤 lib가 링크되고 링크 로더가 뭔지확인, 아래의 경우 libc.so.6가 링크됨. 링크 로더는 /ld-linux-x86-64.so.2

 

프로세스 생성, 명령어 호출, 대기 API

#include <unistd.h>

- pid_t fork(void) : 자식 프로세스 생성. 부모는 자식의 pid 반환, 자식은 0반환. 이후 자식은 exec로 타 작업 수행

- int exec(const char *path) : path 명령어 수행, 반환이 없으나 실패시 -1, errno에 에러코드 등록

  1. 변형 :  execl, execlp, execle 등 존재

  2. l은 실행 인자를 가변으로 전달(마지막엔 NULL), v는 벡터 실행 인자 전달

  3.  e는 마지막에 환경 변수 추가, p는 프로그램을 환경변수 PATH보고 탐색(없으면 절대/상대경로로 찾음)

  4.  ex) execl("/bin/cat", "hello.c", "ls.c", NULL);

#include <sys/types.h>

#include <sys/wait.h>

- pid_t wait(int *status) : 자식 프로세스 중 아무거나가 끝날때까지 기다림. status에는 exit()인자인 종료코드와 종료방법 플래그합친거

- pid_t waitpid(pid_t pid, int *status, int options) : 지정한 pid의 자식이 종료될때까지 기다림. 

 -> 아래는 종료 방법 매크로 

  1. WIFEXITED(status) wait if exited : exit로 종료한 경우 0 아님 -> exit로 종료시 0이 아닌 값이 반환

  2. WEXITSTATUS(status) wait exit status : exit 종료시 종료코드 반환

  3. WIFSIGNALED(status) wait if signaled : 시그널로 종료시 0 아님

  4. WTERMSIG(status) wait term signal?: 시그널 종료 시 시그널 번호 반환

 

프로세스 활용 예제

- spawn.c : 첫째 인자는 실행 프로그램 경로, 둘째 인자는 전달할 인자로 해당 프로그램 실행하는 예제

 

 

프로세스 사이클

#include <stdlib.h>

- void exit(int status) : status는 종료코드(리눅스서 0 성공, 1에러,딴데선 다를수있음), stdio 버퍼 해제(_exit()는 시스템콜)

- 좀비 프로세스 : 종료되었지만 커널 상 남은 ps, fork 시 무조건 wait 사용 or 이중 fork or sigaction으로 해결

  1. 자식 ps가 exit로 끝나면 부모 ps서 wait으로 결과 받음

  2. 부모 ps서 wait이 없거나 늦게 호출할경우 커널이 자식 ps의 상태 보관

  3. 자식은 다끝났는데, 자식 상태를 계속 보관 -> 늘어나면 문제

- 이중 fork

  1. 부모 ps가 fork -> 자식 ps 생성

  2. 자식 ps fork-> 손자 ps 생성한 형태

  3. 자식 ps 종료시 wait로 손자 종료 코드 못받으므로 손자는 정리됨(좀비 ps x)

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    pid_t pid;

    if (argc != 3){
        fprintf(stderr, "Usage : %s <command> <arg>\n", argv[0]);
        exit(1);
    }
    pid = fork();
    if (pid < 0){
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }
    // child process
    if (pid == 0){
        execl(argv[1], argv[1], argv[2], NULL);
        perror(argv[1]);
        exit(99);
    }
    else{
        int status;

        waitpid(pid, &status, 0);
        printf("child (PID=%d) finished; ", pid);
        if (WIFEXITED(status))
            printf("exit, status =%d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("signal, sig=%d\n", WTERMSIG(status));
        else
            printf("abnormal exit\n");
        exit(0);   
    }
}

 

 

파이프

- 프로세스 끼리 연결한 스트림. 부모, 자식 ps간에 pipe로 연결

 #include <unistd.h>

- int pipe(int fds[2]) : 두 ps  파일 디스크립터 fds[0]은 읽기 fds[1]은 쓰기 전용

- 파일디스크립터복제 : int dup(int oldfd), int dup2(int oldfd, int newfd) - 복제로 생성한 fds반환, 새 fds는 기존 fds계속반영

- 3번 파이프를 0번으로 옮기기 : 1. close(0) : 0번 비우기, 2. dup2(3, 0) : fds 3을 fds0 복제, 3. close(3) : 기존 fds 3 해제

#include <stdio.h>

- 위 시스템 콜 파이프는 쓰기 힘듦, stdio서 쉬운거 제공

- FILE *popen(const char *command, const char *mode) : command 프로그램 실행, 파이프 연결, 스트림 반환

- int pclose(FILE *stream) : popen으로 fork, exec 한 자식 ps에 대해 wait 수행,  파일 스트림 해제

 

 

프로세스 관계

1. 부모, 자식 관계 : pstree로 조회 가능, 최 상위 systemd가 커널이 가장 먼저 실행하는 모든 ps 시작점

#include <sys/types.h>

#include <unistd.h>

-> pid_t getpid(void), pid_t getppid(void) : 자식, 부모 pid 취득

2. 타 ps 정보 조회 : ps 파일 시스템 /proc에서 확인 or pstree or ps

3. 프로세스 그룹 & 세션 : 프로세서 세션 - 단말 단위, 프로세스 그룹 - 파이프로 연결된 ps들 단위

 + ps 그룹 리더 : 처음 ps 그룹 만든 ps (최상위 부모) -> PID =PPID

 + ps 세션 리더 : 처음 세션 만든 프로세스, PID = SID(세션 ID)

4. 데몬 PS : 단말이 없는 ps -> 서버 동작용(단말 접속과 무관하게 동작함)

#include <unistd.h>

-> int setpgid(pid_t pid, pid_t pgid) : 프로세스 그룹 설정, 현재 ps로 새 psg 만들 경우 pid와 pgid 둘다 0 설정

-> pid_t setsid(void) : 새 세션 생성, 현재 ps가 세션 리더 * 현재 ps가 psg 리더인경우 세션 리더 설정 불가

 


1. pstree 예시 : WSL 터미널에서 해서 그런가 ps도 거의없고 보통 환경이랑 좀 다르다.

2. /proc에서 보는 ps 상태

3. ps 그룹, 세션 조회 - ps j

4. 모든 프로세스 및 데몬 프로세스 조회 : 데몬은 tty가 없어 ?로 표기됨

 

 

시그널

- 단말(사용자) 혹은 커널이 ps에 통지시 사용(인터럽트와 유사)

- 시그널 전달 시 동작

 1. 무시 (SIGCHLD 시그널, 자식 ps 종료시 전달) -> 무시 대신 시그널을 어떻게 처리(catch)할지 설정도 가능함 

 2. ps 종료 : ctrl + c으로 sigint 발생

 3. core덤프 작성후 종료  : sigsegv, 접근 불가한 메모리 접근시 세그먼테이션 폴트 발생, 코어 덤프 작성(메모리 스냅샷)

 

 

시그널 처리 API

#include <signal.h>

- int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) : sig에대한 시그너 핸들러 act 지정, oldact는 예전핸들러 NULL해도됨

struct sigaction{
	/* sa_handler 혹은 sa_sigaction 둘중 하나만 씀*/
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t*, void*);
    sigset_t sa_mask;
    int sa_flags;
}

- act에 아래의 구조체 사용 해서 시그널 액션 등록. 한번 설정한 시그너 핸들러는 계속 유지, sa_flags에 SA_RESTART 추가시 시스템 콜 재시작(보통 사용), sa_mask로 차단할(블록할) 시그널 지정가능-핸들러 처리중엔 타 sig방지sigemptyset() 

//sigaction 예시
#include <signal.h>

typdef void (*sighandler_t)(int);

sighandler_t trap_signal(int sig, sighandler_t handler){
	struct sigaction act, old;
    
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART;
    if (sigaction(sig, &act, &old) < 0)
    	return NULL;
    return old.sa_handler;
}

 

#include <sys/types.h>

#include <signal.h>

- int kill(pid_t pid, int sig) : 시그널 전송 함수, 해당 pid를 가진 ps에 sig 전달, 이름이 sendsig가 맞으나 오래되서 그럼

- ctrl + c : sig int 전달하여 종료

 

 

 

디렉터리 처리

#include <unistd.h>

- char *getcwd(char *buf, size_t bufsize); : 현재 ps의 경로 출력

- int chdir(const char *path) : ps의 현재디렉터리 변경

 

 

환경변수 처리

- ps에 의해 전파되는 전역변수

- ex : PATH(명령어 찾을 경로), TERM(사용 단말), LANG(사용자 로케일), LOGNAME(로그인명), TEMP(임시파일경로), DISPLAY(x 윈도우 용 기본 디스플레이)

- 전역변수 environ(char** 타입)으로 접근 가능

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

extern char **environ;

int main(int argc, char *argv[])
{
    char **p;

    for(p = environ; *p; p++){
        printf("%s\n", *p);
    }
    exit(0);
}

#include <stdlib.h>

- char *getenv(const char *name) : 해당 이름의 env 가져오기

- chat *putenv(const char *string) : 환경변수 등록 string은 "이름=값"형태

 

 

 

로그인 처리

1. systemd서 단말 갯수만큼 getty 명령어 수행

2. getty는 사용자 입력 기다림 -> login 명령어 시작

3. login cmd로 사용자 인증 -> 셀 시작

- getty 하는일 : 터미널을 open, read해서 사용자 정보 입력받고 login exec -> dup로 0~2번 fds를 새단말과 연결&login반복

- login 하는일 : 사용자 인증, /etc/passwd에 암호 저장(요즘은 보안을 위해 셰도 패스워드 사용)

- 셸 시작 : execl("/bin/sh", "-sh", ...); : 이 명령어로 로그인 셸 실행

https://jhnyang.tistory.com/110

 

 

grep 명령어 

 

- ./grep 정규표현식_패턴 파일명 ...
 
 
정규표현식 규칙
. : 한 문자
* : 선형 패턴 0회 이상 반복
? : 생략가능
\ : 메타문자

 

libc에서 제공하는 정규표현식 API
#include <sys/types.h>
#include <regex.h>
 
int regcomp(regex_t *reg, const char *pattern,
     int flags); : pattern에 든 정규표현식 문자열을 regex_t로 반환, 결과는 reg에
void regfree(regex_t *reg); : regcomp로 확보한 메모리를 해제
int regexec(const regex_t *reg, const char *string,
    size_t nmatch, regmatch_t pmatch[], int flags); : regex_t(패턴) + 문자열로 패턴찾음, 성공시 0 아니면 다른 값 반환
size_t regerror(int errcode, const regex_t *reg,
    char *msgbuf, size_t msgbuf_size); : regcomp서 에러발생시 에러 코드 반환, 이 에러코드를 메시지로 변환

 

 

grep 명령어 구현 코드 및 결과


static void do_grep(regex_t *pat, FILE *src);

int main(int argc, char *argv[])
{
    regex_t pat;
    int err;
    int i;
    
    if (argc < 2)
    {
        fputs("no pattern \n", stderr);
        exit(1);
    }

    err = regcomp(&pat, argv[1], REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
    if (err != 0){
        char buf[1024];
        regerror(err, &pat, buf, sizeof buf);
        puts(buf);
        exit(1);
    }
    if (argc == 2){
        do_grep(&pat, stdin);
    }
    else {
        for (i = 2; i < argc; i++){
            FILE *f;
            
            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_grep(&pat, f);
            fclose(f);
        }
    }
    regfree(&pat);
    exit(0);
}


static void do_grep(regex_t *pat, FILE *src)
{
    char buf[4096];

    while (fgets(buf, sizeof buf, src))
    {
        if (regexec(pat, buf, 0, NULL, 0) == 0){
            fputs(buf, stdout);
        }
    }
}

 

 

리눅스 디렉터리 구조

- 최상위 디렉터리인 루트 /를 시작으로하며, 디렉터리 트리라고도 함.

 

 

/ : 최상위

/bin :  시스템 실행 파일(명령어) 보관  <-> /usr/bin : 일반 사용자를 위한 실행파일

/sbin : 관리자용 명령어

/lib : 시스템  라이브러리 디렉터리       <->  /usr/lib : 유저 라이브러리 디렉터리

/usr : 여러 컴퓨터에서 같이 쓸수있는 sw 파일 보관, /usr/bin, /usr/sbin, /usr/lib, /usr/share

/usr/src : 시스템 실행파일/리눅스 커널 소스코드 보관, 사용자가 직접 작성한건 보관 x

/usr/include : 시스템/커널 헤더파일 보관

/usr/share : 서로 다른 아키텍처에서도 사용가능한(공유가능한) 파일들 존재 ex) man, info 같은 다큐먼트

   -> /usr/share/man 

/usr/local : /usr는 배포판이 관리, /usr/local은 시스템 관리자(사용자) 본인이 관리

/var : 자주 바뀌는거 ex) 로그, 프로세스ID

/etc : 시스템 설정 파일

/dev : 디바이스 파일

/proc : 프로세스 파일 시스템에 의해 프로세스가 파일로 표현됨

/sys : sysfs로 시스템 관련 정보 보관(프로세스가 /proc에 있어 그 외 정보) ex) 디바이스, 디바이스 드라이버

/boot : 리눅스 커널 vmlinuz 보관

/root : su/root의 디렉터리

/tmp, /var/tmp : 임시 파일

 

 

 

 

mkdir, rmdir 함수 API

#include <sys/stat.h>
#include <sys/types.h>

int mkdir(const char *path, mode_t mode); : path로 한 dir 만듬, mode는 권한 지정
- mkdir 에러 종류
ENONET - 상위 디렉터리가 없는경우
ENOTDIR - 상위 디렉터리가 디렉터리가아니라 파일인경우
EEXIST - 이미 존재하는경우
EPERM : 변경 권한이없는경우
#include <unistd.h>
int rmdir(const char *path);
 
 
umask
 
mode_t umask(mode_t mask); : ps의 umask를 mask로 변경, 이전의 umask 반환, 절대 실패 x
 * umask : ps 속성으로 8진수 보통 022, mode(파일,dir 권한)가 그대로 사용되지 않고 umask 적용된 결과가 사용됨.
        open(), mkdir()시 mode & ~umask한게 실제 권한으로 사용됨  
                  rwx rwx rwx
    mode     777 : 111 111 111
    umask   022 : 000 010 010

    real       755 : 111 101 101

 

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char *argv[]){
    int i;

    if (argc < 2){
        fprintf(stderr, "%s : no argument\n", argv[0]);
        exit(1);
    }
    for (i=1; i < argc; i++){
        if (mkdir(argv[i], 0777) <0){
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc, char *argv[]){
    int i;
    if (argc < 2){
        fprintf(stderr, "%s : no arguments\n", argv[0]);
        exit(1);
    }
    for (i = 1 ; i < argc; i++){
        if (rmdir(argv[i]) < 0){
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}

 

 

 

 

 

 

 

stdio

- 시스템 콜 함수인 read(), write()의 대안으로 바이트 단위를 읽던것과는 달리 독자적인 버퍼를 사용해 커널 스트림 줄/여러바이트 단위로 빠르게 입출력 가능

- FILE *fopen(const char *path, const char *mode) : open()과 유사하나 FILE 타입을 반환

- int fclose(FILE *stream), int fgetc(FILE *stream), int fputc(int c, FILE *stream);

- 줄단위 입출력 : 1줄 혹은 size -1만큼 읽고 쓰기 fgets(), fputs() 

- 고정 길이 입출력 : size_t fread(void *buf, size_t size, size_t nmeb, FILE * stream) - size x nmemb 바이트 읽어 buf쓰기

 * stdio에서 제공하는 fread, fwrite -> 시스템 콜 read, write랑 비슷하지만 stdio꺼라 다양한 환경서 사용 가능

- 파일 오프셋 처리 : fseek, fseeko(후자는 64비트 고려한 오프셋), ftell, ftello - 파일 오프셋 반환, rewind - 오프셋 처음으로

- file 타입 : int fileno(FILE *stream) 해당 파일 스트림의 디스크립터 반환, FILE *fdopen(int fd, const char *mode) 파일디스크립터로 스트림 포인터 반환

- 버퍼링 : fflush(FILE *stream) - 버퍼 데이터 write

- EOF, 에러 : feof(FILE *stream) - 스트림 EOF 플래그 가져옴, EOF 도달시 0이아닌 값나옴. ferror 스트림 에러 플래그 가져옴, 에러와 EOF 구분 불가시 사용, clearerr 에러와 EOF 플래그를 지움 -> 이런 상황 발생시 클리어하여 다시 읽기 가능 

 

 

head

- 처음 몇줄만 출력하는 명령어

- 버전 1 : 파일 지정 x

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

static void do_head(FILE *f, long nlines);

int main(int argc, char * argv[])
{
    if (argc != 2)
    {   
        fprintf(stderr, "Usage: %s n\n", argv[0]);
        exit(1);
    }   
    do_head(stdin, atol(argv[1]));
    exit(0);
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }    
}

- 버전 2 : 파일 지정 o + 여러개

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

static void do_head(FILE *f, long nlines);

int main(int argc, char * argv[])
{
    long nlines;

    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s [file file ...]\n", argv[0]);
        exit(1);
    }
    nlines = atol(argv[1]);
    if (argc == 2)
        do_head(stdin, nlines);
    else{
        int i;

        for (i = 2; i < argc; i++){
            FILE *f;
            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_head(f, nlines);
            fclose(f);
        }
    }
    exit(0);
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }
}

옵션 파싱 : 짧은 옵션 get opt()

- 짧은 옵션 : -p, -a

- 긴 옵션 : --version, --help

- int getopt(int argc, char * const argv[], const char *optdecl) : 유닉스 OS 옵션해석 API, 짧은 옵션만 인식, 잘못된 옵션은 ?반환,

extern char *optarg;

extern int optind, opterr, optopt;

- 파라미터를 받지 않는 옵션으로 -a, -t, -x가 있는 경우 optdecl는 atx(순서 무관)

- 파라미터를 받는 옵션이 있는 경우 옵션문자 다음에 콜론 : 사용(f가 옵션받을시) : af:tx or atxf: or f:atx

- getopt 관련 전역변수 : optarg(현재옵션 파라미터), optind(현재 옵션인덱스), opterr(참인경우 에러 표시), optopt(현재 처리중인 옵션)

#include <unistd.h>

int main(int argc, char *argv[])
{
	int opt;
    while ((opt = getopt(argc, argv, "af:tx")) != -1)
    {
    	switch (opt)
        {
        	case 'a':
            	//a일떄 코드
                break;
           	case 'f':
            	//f일떄 코드
                break;
            .....
            case '?':
            	//잘못된 옵션이 전달될 때 코드
            	break;
        }
    }
    // 프로그램 본체
}

옵션 파싱 2 : 긴 옵션 getopt_long

#define _GNU_SOURCE

#icnlude <getopt.h>



int get_long(int argc, char * const argv[], const char *optdecl, const struct option *longoptdecl, int *longindex);

struct option{
	const char *name;
    int has_arg;
    int *flags;
    int val;
};

extern char *optarg;
extern int optind, opterr, optopt;

 

 

옵션 처리가 가능한 head

#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <getopt.h>

static void do_head(FILE *f, long nlines);
#define DEFUALT_N_LINES 10

static struct option longopts[] = {
    {"lines", required_argument, NULL, 'n'},
    {"help", no_argument, NULL, 'n'},
    {0, 0, 0, 0}
};

int main(int argc, char * argv[])
{
    int opt;
    long nlines = DEFUALT_N_LINES;


    while ((opt = getopt_long(argc, argv, "n:", longopts, NULL)) != -1){
        switch (opt){
            case 'n': // -n == --lines
                nlines = atol(optarg);
                break;
            case 'h':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
            case '?':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
        }
    }
    
    if (optind == argc){
        do_head(stdin, nlines);
    }
    else {
        int i;

        for (i = optind; i < argc; i++){
            FILE *f;

            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_head(f, nlines);
            fclose(f);
        }
    }
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }        
}

 

 

 

gdb로 디버깅하기

- 위 코드의     while ((opt = getopt_long(argc, argv, "n:", longopts, NULL)) != -1){ 를

    while ((opt = getopt_long(argc, argv, "n", longopts, NULL)) != -1){로 바꾸자(n:에서 :를 뺏다)

 -> :을 뺏으므로 이 옵션은 파라미터를 받지 않는다는 의미. 실제로는 이옵션이 있을때 몇 줄인지 받아야함.

int main(int argc, char * argv[])
{
    int opt;
    long nlines = DEFUALT_N_LINES;


    while ((opt = getopt_long(argc, argv, "n", longopts, NULL)) != -1){
        switch (opt){
            case 'n': // -n == --lines
                nlines = atol(optarg);
                break;
            case 'h':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
            case '?':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
        }
    }

- gdb 실행 : gdb ./바이너리

- 옵션을 줘서 프로그램 시작 : run -n 5

 -> stdlib(libc)의 __GI_____strtol_l_internal 함수에서 세그먼테이션 폴트 발생

- bactrace 함수 호출 과정 보기 : __GL____strtol_l_internal에서 뒤로 돌아가면서 어떻게 함수들이 호출됬는지 볼 수 있음

 -> head3_debug.c 의 24번째 줄에서 strtol_c로 넘어감 -> 24번째 줄이 문제이므로 보기

- frame(backtrace 해당 줄 보기) : frame 1로 본 24번째 줄은 다음과 같음

- list : 해당 줄 위아래 5줄씩 주위를 보기

- print {변수명} : 해당 변수 값 보기

 -> 원래 optarg에는 -n 5를 하면서 5가 들어갔어야 하나 21번줄에 파라미터가 있음을 의미하는 n: 가아닌 파라미터가 없다는 의미인 n이 들어가 optarg에 파라미터 5가 입력되지않음.

- quit : gdb 종료

리눅스 운영체제 주요 개념

- 파일 시스템, 프로세스, 스트림

 

운영체제 개요

- 역활 : 하드웨어, SW 자원 관리, 시스템 콜 등

- 운영체제 패키지 : 셸, util-linx, procps, gnu coreutils/grep/diffutils/libc, libs, 개발 환경, X window

- 커널 : 리눅스 핵심, vmlinuz 파일 /boot 

- 디바이스 드라이버 : CPU, ram, 그래픽/네트워크 어뎁터 등 다양한 종류의 특정 디바이스을 제어하기 위한 SW

- 시스템콜 : 응용 어플이 간접적으로 하드웨어 제어를 하도록 제공하는 API로 커널을 불름(call), open, read, write, fork 등

 

파일 시스템

- 파일 종류 : 일반 파일, 디렉터리, 심볼릭 링크(바로가기), 디바이스 파일(디바이스를 파일로 인식), 파이프, 소켓

- 메타 정보 : 파일이 가진 데이터 외 정보 - 종류, 권한, 크기 등

- 파일 시스템 종류 : ext4(리눅스 파일시스템), nfs(네트워크 파일 시스템)

 

프로세스

- 실행중인 프로그램의 동작 단위 

- 프로세스 ID : 프로세스 구분하기 위한 식별자, 프로그램 한개에 여러 프로세스 존재 가능

- 시그널 : 프로세스 제어에 사용되는 신호

 

스트림

- 바이트 스트림 : 바이트들이 흘러가는 통로, 파일을 통해 이뤄짐

- 종류 : file, 스트림 커널

- 용도 : 파일 입출력, 파이프(프로세스 사이의 스트림), 디바이스 스트림(장치 입출력), 네트워크통신->프로세스간 통신IPC

 

 

다중 사용자 시스템

- 윈도우, 리눅스 <-> 안드로이드, IOS(단일 사용자 시스템)

- 다중 사용자 시스템 사용 이유 : 다중 사용자를 고려해서 설계하는 것이 더 안전하므로

 

단말 개요와 종류

- 구성 요소의 가장 끝단 terminal에 위치 한 장치

- 텔레타이프 : 초기 디스플레이가 없는 텔레타이프(teletype)를 이용. 여기서 tty라는 이름이 사용

- thumb terminal : 문자만 표기가능한 디스플레이가 추가됨.

- X termnial : 요즘 모니터(비트맵 디스플레이)를 탑제한 단말. X 윈도우 시스템 클라이언트로 화면 출력

 * X 윈도우 시스템(X11) : 서버-클라이언트 모델의 GUI. 클라이언트가 x 서버에 접속해서 요청하면 결과를 화면에띄워줌

- 단말 에뮬레이터 : 이전에 하드웨어로 만든 단말을 SW로 구현한 것으로 우분투 터미널, iTerm 같은것들

- 단말의 변화 흐름 : 텔레타이프 -> 문자 터미널 -> 비트맵 터미널 -> 단말 에뮬레이터

- 가상 콘솔(=터미널=단말) : 리눅스서 여러 가상콘솔동작 X 윈도우가 tty7 점유, [ctrl] + [alt] + [fn키]로 다른 tty로 변경 가능


텔레 타이프 : 출력이 종이에 적혀 나온다

덤 터미널 thumb terminal : 문자만 출력, VT100

NCD-X terminal : 비트맵 디스플레이를 이용

단말 에뮬레이터 : SW로 가상화한 터미널, 위는 iterm

파일, 디바이스, 단말

- 리눅스에선 모든 디바이스, 단말이 파일로 표현하여, 스트림으로 입출력 가능

- /dev 경로에 /dev/tty0, /dev/tty1로 존재.

+ 셸 : 명령어 해석기

 

 

스트림 시스템 콜

- 파일 디스트립터 : 커널이 만들어준 스트림 번호로 입출력시 사용

#include <unistd.h>

- ssize_t read(int fd, void *buf, size_t bufsize); : fd 읽어서 buf에 저장, ssize_t는 부호있는 정수(여러 플랫폼에 따라서 정수 크기가 다를 수 있음), size_t 부호 없는 정수, 읽은 크기 반환, 0은 끝 도달, -1은 에러, read()는 \0이 끝에 있는걸 전재 x

#include <unistd.h>

- ssize_t write(int fd, const *buf, size_t bufsize) : bufsize 만큼 읽어 fd에 씀.

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

- int open(const char *path, int flags, (mode_t mode)) : mode는 create 플래그시 권한 지정

#include <unistd.h>

- int close(int fd) : fd 스트림 해제

- off_t lseek(int fd, off_t offset, int whence) : 파일 디스크립터 파일 오프셋 이동, whence 플래그(SEEK_SET : 파일 처음 기준, SEEK_CUR : 현재 기준, SEEK_END 끝 기준), lseek의 l은 Long 타입으로 seek()도 존재

- int dup(int oldfd), int dup2(int oldfd, int newfd) : oldfd를 복제

#include <sys/ioctl.h>

- int ioctl(int fd, unsigned long request, ... ) : 디바이스 처리 관련 시스템 콜 ex : dvd 여닫기, 프린터 구동, 단말 통신속도 조절 등, request는 작업을 의미하는 상수, ...는 가변 인자로 여러개 지정 가능

- int fcntl(int fd, int cmd, ...) : 파일 디스크립터 관련 작업 위주의 시스템 콜(ioctl과 구분) 

 

 

 

cat 명령어 구현하기

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

static void do_cat(const char *path);
static void die(const char *s);

int main(int argc, char *argv[])
{
    int i;
    if (argc < 2)
    {
        fprintf(stderr, "%s : file name not given\n", argv[0]);
        exit(1);
    }
    for (i=1; i< argc; i++)
    {
        do_cat(argv[i]);
    }
    exit(0);
}

#define BUFFER_SIZE 2048

static void do_cat(const char *path)
{
    int fd;
    unsigned char buf[BUFFER_SIZE];
    int n;

    fd = open(path, O_RDONLY);
    if (fd < 0) die(path);
    for (;;)
    {
        n = read(fd, buf, sizeof buf);
        if (n < 0) die(path);
        if (n == 0) break;
        //STDOUT_FILENO : standard output stream = print buf data 
        //STDOUT_FILENO(is linux file descrypter in unistd.h)
        //stdout is c standard io stream(in stdio.h, similar with STDOUT_FILENO)
        if (write(STDOUT_FILENO, buf, n) < 0) die(path);
    }

    if (close(fd) <0) die(path);
}

static void die(const char *s)
{
    perror(s);
    exit(1);
}

* exit(0) : 정상 종료, exit(1) : 오류 종료, return과의 차이 : exit 즉시 프로세스 종료, return은 뒷 내용 마친 후 종료

 

cat 명령어 실행 결과

+ Recent posts