본문 바로가기

컴퓨터 과학/리눅스 프로그래밍

[이론]리눅스 프로그래밍 - 프로세스와 쓰레드

반응형

 

프로세스 : 실행 중인 프로그램
    생성(new) : 프로세스가 생성됨
    준비(ready) : CPU의 할당을 기달기고 있는 상태
    실행(execute) : CPU에 할당되어 실행되고 있는 상태, 커널에게 시스템을 수행하게 할 수 있음
    대기(sleeping) : 커널에 시스템 호출을 처리하기 위해 기다리는 상태
    종료(terminated) : 프로세스 실행이 완료된 상태

    PCB(Process Control Block)에 의해 관리된다.

프로세스 확인 : ps
프로세스 종료 : kill or killall(-9 : SIGKILL)

시그널 : 소프트웨어적인 인터럽트, 하드웨어적인 시그널과 다르나 비슷한 면이 있다
                      
signal(), pause()

signal() : 함수는 특정 시그널에 대해 처리 함수를 등록합니다. 
    즉, 해당 시그널이 발생했을 때 호출될 시그널 핸들러(처리 함수) 를 지정합니다.
    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
시그널은 공유되지 않는다.(사용자가 공유하지 않는 이상)

개선 버전 : sigaction(int signum, const struct sigaction* act, const struct sigaction* oldact)
struct sigaction{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t*, void*); //siginfo_t
    int sa_flags;
    void (*sa_restorer)(void) //일반적으로 사용하지 않음
}

pause() : 시그널이 도착할 때까지 현재 프로세스를 일시 중단(sleep)시킵니다.

    #include <unistd.h>
    int pause(void);

코드 내에서 시그널을 발생시킬 수 있다.

1 SIGHUP 종료 터미널이 종료될 때 전송됨. 데몬 프로세스에서는 설정 파일 재로딩 트리거용으로 자주 사용됨
2 SIGINT 종료 인터럽트 시그널. Ctrl+C 입력 시 전송됨
3 SIGQUIT 종료 + 코어 덤프 Ctrl+\ 입력 시 전송. 디버깅 시 사용됨
6 SIGABRT 종료 + 코어 덤프 abort() 함수 호출 시 발생. 런타임 에러, assertion 실패 등
9 SIGKILL 강제 종료 (무시 불가) 강제 종료 시그널. 무시하거나 핸들링할 수 없음
11 SIGSEGV 종료 + 코어 덤프 잘못된 메모리 접근 (Segmentation Fault)
13 SIGPIPE 종료 파이프의 상대 프로세스가 종료된 상태에서 쓰기 시도 시 발생
14 SIGALRM 종료 alarm() 또는 setitimer() 설정된 시간이 만료될 때 발생
15 SIGTERM 종료 일반적인 종료 요청. 핸들링 가능
17 / 20 / 21 SIGCHLD 무시 (기본) 자식 프로세스 종료 시 부모에게 알림
18 / 19 SIGCONT 계속 정지된 프로세스를 다시 실행
19 / 17 / 18 SIGSTOP 강제 정지 (무시 불가) 프로세스를 정지시킴. 핸들링 및 무시 불가
20 / 18 / 19 SIGTSTP 정지 Ctrl+Z 입력 시 전송. 핸들링 가능
21 SIGTTIN 정지 백그라운드 프로세스가 터미널로부터 입력 시도 시 발생
22 SIGTTOU 정지 백그라운드 프로세스가 터미널로 출력 시도 시 발생
23 SIGURG 무시 소켓에 긴급 데이터 수신 시 발생
24 SIGXCPU 종료 CPU 시간 초과 시 발생 (ulimit -t로 설정 가능)
25 SIGXFSZ 종료 파일 크기 제한 초과 시 발생 (ulimit -f로 설정 가능)
26 SIGVTALRM 종료 가상 타이머 만료 시 발생 (실제 CPU 시간 기준)
27 SIGPROF 종료 프로파일링 타이머 만료 시 발생 (시스템 + 사용자 시간 기준)
28 SIGWINCH 무시 (기본) 터미널 창 크기 변경 시 발생. curses/ncurses 라이브러리 등에서 사용됨


프로세스 : fork()(같은 내용의 프로세스, 새로운 PID 동종의 자식 프로세스), 
    exec(다른 내용의 프로세스, 프로세의 내용을 변경하지만 PID는 변경되지 않는다.)
    exit(), _exit() : 프로세스를 종료한다. return으로 끝내는 것 같지만 실질적으로 exit()가 호출된다.



fork, exec
반환값
0: 자식 프로세스에게 반환
양의 정수 (pid): 부모 프로세스에게 반환되며, 이는 자식 프로세스의 PID
-1: 오류 발생 시 반환

부모와 자식은 거의 동일한 주소 공간을 복사하여 각각 독립적으로 실행
전역변수, 지역변수, 힙 영역은 복사되지만 공유되지 않음 (Copy-On-Write)
파일 디스크립터, 환경변수, 코드 영역 등은 공유될 수 있음

fork()는 프로그램을 처음부터 다시 실행시키는 것이 아님
fork() 호출 "직후"부터 부모와 자식이 분기되어 각각의 경로로 실행됨
코드에서 main()을 다시 실행하거나 초기화 루틴이 재진입하지 않음

wait 함수
wait()(모든 자식 프로세스들을 대기), waitpid(특정 자식 프로세스 지정 대기)

프로세스 간 통신(Inter-Process-Communication)
프로세스는 일반적으로 데이터 영역을 공유하지 않는다. => 프로세스 간 협응을 위해서는 별도의 통신 수단이 필요하다.

1) 버퍼용 파일을 만들어서 사용한다. 
    > 하나의 프로세스만 쓸 수 있고, 어떤 프로세스가 쓰고 있는지 확인 필요
2) 파일 시스템을 경유하지 않는 IPC 시스템 ***

파이프(Pipe) : 유닉스의 프로세스 간 단방향 통신 메커니즘, 
    부모 프로세스-자식 프로세스 간 통신에 주로 사용

int pipe(int pipefd[2]);
    pipefd[0]: 읽기 전용(Read End)  
    pipefd[1]: 쓰기 전용(Write End)
    성공 시: 0
    실패 시: -1

int pipe2(int pipefd[2], int flags);
    flags 인자를 통해 파일 디스크립터의 동작을 제어 가능

O_NONBLOCK Non-blocking I/O 설정. 입출력 작업이 블로킹 없이 즉시 리턴하도록 설정
O_CLOEXEC exec() 계열 함수 실행 시 해당 파일 디스크립터 자동 닫힘 설정
O_DIRECT (지원 여부 시스템에 따라 다름) 직접 I/O 수행 요청. 파일에 대한 캐시를 우회하고 실제 디스크와 직접 I/O를 수행




Named Pipe
FIFO : 파이프와 유사한 단방향 메커니즘, but 부모-자식 관계가 아니어도 통신 가능
    FIFO 파일 생성,but 데이터 전송에 사용되는 것이 아닌 채널 확립을 위한 것, 
    파일 디스크립터를 이용한 통신

mkfifo(const char *path, mode_t mode)
    pathname: 생성할 FIFO 파일의 경로 (예: /tmp/myfifo)
    mode: 생성되는 FIFO의 접근 권한 (파일 모드, 예: 0666)
    성공 : 0, 실패 : -1
    일반 파일처럼 경로를 갖지만, 읽기, 쓰기 시 데이터가 커널 버퍼를 통해 흐름
    읽기/쓰기 시 블로킹될 수 있음:
    읽는 쪽이 없는데 쓰려 하면, 쓰기 호출은 블로킹됨.
    쓰는 쪽이 없는데 읽으려 하면, 읽기 호출은 블로킹됨.

FIFO IPC와 일반 파일 IPC 차이
1. FIFO(이름 있는 파이프)를 이용한 IPC
(1) 개요
FIFO는 "이름 있는 파이프(named pipe)"라고도 하며, 특수한 형태의 파일입니다.
일반적으로 mkfifo() 시스템 콜을 통해 생성하며, /tmp/myfifo와 같은 파일 경로를 통해 접근할 수 있습니다.
FIFO는 커널 공간에서 순차적으로 데이터를 저장하며, 한 프로세스가 쓰면 다른 프로세스가 읽을 수 있는 구조입니다.

(2) 특성
단방향 혹은 양방향 통신 가능: 한 쪽 프로세스가 쓰고 다른 한 쪽이 읽는 구조. 양방향 통신 시 FIFO 두 개를 생성하여 각각의 방향으로 사용.
버퍼 기반 스트림 통신: FIFO는 데이터를 일시적으로 커널 버퍼에 저장하며, 데이터를 순차적으로 처리함.
동기화 지원: FIFO는 블로킹/논블로킹 모드를 지원하여, 읽는 프로세스가 없으면 쓰기 블로킹 등 동기화가 자동으로 이뤄짐.
일시적인 데이터 유지: 데이터를 읽으면 FIFO 버퍼에서 제거되므로 일회성 데이터 전달에 적합.

2. 일반 파일을 이용한 IPC
(1) 개요
일반적인 디스크 기반 파일(open, read, write)을 사용하여 두 프로세스가 동일한 파일을 통해 데이터를 주고받음.
특별한 시스템 콜 없이 기존 파일 시스템 기능을 이용함.

(2) 특성
지속성: 일반 파일은 디스크에 저장되므로 전원이 꺼지거나 프로세스가 종료되어도 데이터가 유지됨.
명시적 동기화 필요: 파일 락(flock, fcntl 등)을 통해 쓰기/읽기 충돌 방지 필요.
랜덤 접근 가능: 파일 오프셋 조작을 통해 임의 위치 접근 가능. 스트림 통신이 아닌 구조화된 데이터 저장에 적합.
다중 프로세스 접근 지원은 제한적: 동기화 없이는 race condition, dirty read/write 등이 발생할 수 있음.

유닉스 시스템의 V/XSI의 IPC 함수
메시지 큐 : 메시지 단위의 IO 수행, 서버와 클라이언트가 동시간대에 있을 필요 없음
세마포어 : 프로세스 동기화에 필요한 공유 변수와 같은 공유된 리소스 제한을 위한 세마포어 참조와 조작을 위해 만들어진 IPC
공유 메모리 : 커널과 프로세스 사이에 메모리 복사를 2회 줄여 IPC 성능 향상, 별도의 동기화 메커니즘이 필요하다.

IPC 종류 헤더 파일 IPC 채널 생성/열기 IPC 채널 제어/삭제 IPC 동작 함수
메시지 큐 (Message Queue) <sys/ipc.h><sys/msg.h> msgget(key_t key, int msgflg) msgctl(int msqid, int cmd, struct msqid_ds *buf)→ IPC_RMID: 제거 msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
세마포어 (Semaphore) <sys/ipc.h><sys/sem.h> semget(key_t key, int nsems, int semflg) semctl(int semid, int semnum, int cmd, union semun arg)→ IPC_RMID: 제거 semop(int semid, struct sembuf *sops, size_t nsops)
공유 메모리 (Shared Memory) <sys/ipc.h><sys/shm.h> shmget(key_t key, size_t size, int shmflg) shmctl(int shmid, int cmd, struct shmid_ds *buf)→ IPC_RMID: 제거 shmat(int shmid, const void *shmaddr, int shmflg)shmdt(const void *shmaddr)



key_t ftok(const char *pathname, int proj_id);

pathname : 존재하는 실제 파일 경로
proj_id : 1~255 사이의 정수형 ID (8비트 값). 사용자가 구분 용도로 선택

성공: 고유한 IPC 키 (key_t) 반환
실패: -1 반환, errno 설정됨

POSIX IPC 함수
POSIX message Queue
Semaphore

POSIX 스레드 동기화
프로세스 간 통신 기능이 있는 것과 별개로 번거롭다 => 쓰레드는 이 문제를 해결해준다.
쓰레드는 별개의 스택을 가지지만 힙, 데이터, 텍스트는 구분된다.
1:1, M:1, M:N (사용자 쓰레드 : 커널 쓰레드)

반응형