article thumbnail image
Published 2023. 1. 5. 23:06

1. 설명에 들어가기 전 후기

minishell을 하기 전, pipex와 minitalk 둘 중에 하나만 고르면 되는데 나는 minitalk을 골랐다

minishell을 하려면 pipex가 도움이 된다고는 하지만 어차피 둘 다 알아야 하고, 이전에 minitalk을 평가해봤을때 프로세스 간 통신이라는 것이 인상 깊어서 minitalk을 선택했다

minitalk은 보너스를 하지 않으면 정말 쉬웠다

보너스를 하게 된다면 sigaction과 3 way handshake를 해야한다는데, 이론으로 들었던 3 way handshake를 직접 구현하지 못한게 아쉽다

나중에 블랙홀의 여유가 되었을 때 minitalk 만큼은 보너스를 다시 해보고 싶다는 생각이 들었다

minitalk을 하면서 든 생각은 운영체제 공부 좀 열심히 할걸....이다

운영체제 시간에 배웠던 프로세스, 비동기, 인터럽터 등 정말 많은 내용을 이 과제에서 마주치게 되었다

철학자 과제를 하면서 운영체제 공부를 다시 해야할 것 같다... :)

 

2. 프로젝트 소개

프로세스 간 통신하기

server에서 pid를 출력하면 client는 해당 pid에 원하는 문자열을 송신.

server에서 client가 보낸 문자열을 수신하여 출력하는 프로젝트

 

3. 허용 함수

signal, sigemptyset, sigaddset, sigaction, kill, getpid, pause, sleep, usleep

pid 출력

pid_t getpid(void); //pid 출력

항상 성공하므로 에러처리를 할 필요가 없음

pid_t : 프로세스 번호(pid)를 저장하는 타입(t) 

 

스레드 정지

unsigned int sleep(unsigned int seconds); //초 만큼 대기
int	usleep(useconds_t microseconds); //마이크로초 만큼 대기
int pause(void); //시그널이 올 때 까지 대기

 

시그널 전송

int kill(pid_t pid, int sig); //지정된 프로세스로 시그널 전송
  • pid > 0 : 지정 프로세스로 시그널 전송
  • pid == 0 : 호출 프로세스 그룹의 모든 프로세스로 시그널 전송
  • pid == -1 : 본인을 제외한 호출 프로세스 그룹의 모든 프로세스로 시그널 전송
  • pid < -1 : 지정 프로세스 그룹의 모든 프로세스로 시그널 전송

반환값

  • 0 : 성공
  • -1 : EINVAL (유효하지 않는 시그널)
  • -1 : EPERM (호출 프로세스에서 목표 프로세스에 시그널을 보낼 권한 없음)
  • -1 : ESRCH (목표 프로세스 또는 그룹이 존재하지 않음)

 

시그널 집합 설정

int sigemptyset(sigset_t *set); //시그널 집합에서 모든 시그널 제거
int sigaddset(sigset_t *set, int signo); //시그널 집합에서 특정 시그널 추가

 

시그널 핸들링

void (*signal(int sig, void (*func)(int)))(int); //시그널 처리를 결정하는 간단한 함수, 운영체제에 따라 다르게 동작
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //시그널 처리를 결정하는 상세한 함수, 운영체제의 차이가 없어서 안정적

 

4. 시그널

소프트웨어 인터럽트로 프로세스에 무엇인가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것

 

*인터럽트 : 예상치 못한 이벤트

*동기적 vs 비동기적

동기적 : 어떤 작업을 요청했을때 그 작업이 종료될때 까지 기다린 후 다음 작업을 수행하는 방식

비동기적 : 어떤 작업을 요청했을 떄 그 작업이 종료될때 까지 기다리지 않고 다른 작업을 하고 있다가, 요청했던 작업이 종료되면 그에 대한 추가 작업을 수행하는 방식

 

4.1. 시그널 동작 방식

1. signal() 또는 sigaction() 함수로 시그널 핸들러 등록

2. 유저 모드에서 인터럽트가 발생되면 커널 모드로 진입하여 등록된 시그널 핸들러 수행

3. 모든 처리가 완료되면 다시 정상 프로그램 흐름으로 돌아감

 

4.2. 시그널 종류

  • SIGHUP : HangUp. 접속을 끊을 때, 터미널과 연결이 끊어졌을 때
  • SIGINT : Interrupt. 현재 작동 중인 프로그램의 동작을 멈출 때
  • SIGQUIT : Quit. 사용자가 종료키를 누를때
  • SIGABRT : Abot. Abot함수에 의해 발생할 때
  • SIGKILL : Kill. 실행 중인 프로세스를 강제 종료할 때
  • SIGSEGV : Segmetation Violation. 메모리 액세스가 잘못되었을 떄
  • SIGPIPE : 종료된 소켓에 쓰기를 시도할 때
  • SIGALRM : 알람 타이머 만료 시에 사용
  • SIGTERM : Terminate. 정상적인 종료 방법
  • SIGCHLD : 자식 프로세스가 종료할 때
  • SIGCONT : 중지된 프로세스를 실행할 때
  • SIGSTOP : SIGCONT 시그널을 받을 때 
  • SIGTSTP : 프로세스 대기로 전환할 때
  • SIGUSR1 : 사용자 정의 신호
  • SIGUSR2 : 사용자 정의 신호

...... 등등

 

4.3. sigaction

시그널 핸들러에 대한 정보를 가지고 있는 자료 구조체

struct sigaction {
	union __sigaction_u __sigaction_u;
    sigset_t sa_mask; //핸들러 실행 중 블록될 시그널 집합 설정
    int sa_flags;
};
   
  union __sigaction_u {
  	void (*__sa_handler)(int); //signal 함수를 위한 간단한 함수 포인터
    void (*__sa_handler)(int, siginfo_t *, void *); //sigaction 함수를 위한 상세한 함수 포인터
}

sa_flags 종류

  • SA_ONSTACK : sigalstack()에 설치된 대체 스택을 사용해 이 시그널에 대한 핸들러를 실행
  • SA_ONESHOT : 시그널 기본 처리방법은 SIG_DEL로 재설정되고 시그널이 처리되는 동안 시그널을 블록하지 않음
  • SA_NOMASK : 시그널이 처리되는 동안 유닉스 커널에서 해당 시그널을 자동으로 블록하지 않음
  • SA_RESTART : 시그널 핸들러에 의해 중지된 함수를 재시작
  • SA_SIGINFO : 시그널에 대한 더 많은 정보를 제공하는 추가적인 인자로 시그널 핸들러를 실행
  • SA_NOCLDWAIT : SIGCHLD인 경우에, 자식들이 제거될 때 좀비 프로세스로 만들지 않음
  • SA_NOCLDSTOP : SIGCHLD라면, 자식 프로세스가 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 등을 받아서 중단되었을 때 부모 프로세스에 SIGCHLD 시그널을 전달하지 않음

 

4.4. siginfo_t

시그널에 대한 정보를 담고 있는 자료구조

siginfo_t {
	int si_signo; //시그널 넘버
    int si_errno; //errno 값
    int si_code; //시그널 코드
    pid_t si_pid; //프로세스 id 보내기
    uid_t si_uid; //프로세스를 전송하는 실제 사용자 id
    int si_status; //exit 값 또는 시그널
    clock_t si_utime; //소모된 사용자 시간
    clock_t si_stime; //소모된 시스템 시간
    sigval_t si_value; //시그널 값
    int si_int; //POSIX.1b 시그널
    void *si_ptr; //POSIX.1b 시그널
    void *si_addr; //실패를 초래한 메모리 위치
    int si_band; //밴드 이벤트
    int si_fd; //파일 기술자
}

 

5. 프로세스

실행중인 프로그램

 

운영체제가 빠르게 cpu가 실행할 프로세스를 교체하고 있기에, 동시에 여러개의 프로세스를 실행할 수 있음

프로세스에 대한 정보는 프로세스 제어 블록(PCB)라는 자료구조에 저장됨

  • PID
    • 운영체제가 각 프로세스를 식별하기 위해 부여된 프로세스 식별 번호
    • mac os에서 범위는 100 ~ 99998
  • 프로세스 상태
    • 프로세스의 상태를 저장
  • 스케줄링 우선순위
    • 스케줄링이란 운영체제에서 여러 개의 프로세스가 CPU에서 실행되는 순서를 결정하는 것
    • 우선순위가 높으면 먼저 실행될 수 있음
  • 권한
    • 프로세스가 접근할 수 있는 자원을 결정하는 정보
  • 프로세스의 부모와 자식 프로세스
    • 최초 생성되는 init 프로세스를 제외하고 모든 프로세스는 부모 프로세스를 복제해서 생성되며, 트리 계층 관계를 형성
    • 따라서 각 프로세스는 자식 프로세스와 부모 프로세스에 대한 정보를 가지고 있음
  • 프로세스의 데이터와 명령어가 있는 메모리 위치를 가리키는 포인터
  • 프로세스에 할당된 자원들을 가리키는 포인터
  • 실행 문맥
    • 프로세스가 실행상태에서 마지막으로 실행한 프로세스의 레지스터 내용을 담고 있음

 

6. 직렬 통신 vs 병렬 통신

 

6.1. 직렬 통신

순차적으로 데이터를 한번에 하나의 비트만을 전송

양단간 통신 거리가 먼 경우에도 사용하기 용이

구현이 비교적으로 쉬움

 

6.2. 병렬 통신

여러 개의 병렬 채널 위로 동시에 여러 개의 데이터 신호를 보내는 방식

양단간 통신 거리가 먼 경우에는 사용하기 어려움

구현이 어려움

비용이 많이 듦

선의 부피가 커지고, 간섭이 일어나기 쉬움

 

7. 비트 연산자

설명 설명
& 대응되는 비트가 모두 1이면 1을 반환 (AND 연산)
| 대응되는 비트 중에서 하나라도 1이면 1을 반환 (OR 연산)
^ 대응되는 비트가 서로 다르면 1을 반환 (XOR 연산)
~ 비트를 1이면 0으로, 0이면 1로 반전 (NOT 연산)
<< 지정한 수 만큼 비트들을 전부 왼쪽으로 이동 (left shift 연산)
>> 부호를 유지하면서 지정한 수 만큼 비트들을 전부 오른쪽으로 이동 (right shift 연산)

 

8. 동작 순서

8.1. client

while (j < 8)
{
	bit = 'h' >> (7 - j) & 1;
    
    if (bit == 0)
    	kill(pid, SIGUSR1);
    else if (bit == 1)
    	kill(pid, SIGUSR2);
    j++;
}

만일 hello를 서버에 보낸다고 한다면 클라이언트에서는 각각의 글자들에 대해서 8번씩 위의 과정을 진행한다

위의 과정은 h에 대한 과정이다

h의 아스키코드는 104. 이를 2진수로 바꾸면 01101000이다

즉, 한 글자에 대해 2진수 값을 하나씩 총 8번 서버에 보내는 과정이다

'h' >> (7 - j) & 1은 시프트 연산을 앞에서부터 하나씩 0인지 1인지 구분하는 과정이다

  • j = 0일 때, 'h' >> (7 - 0)은 0, 0 & 1은 0이므로 서버에 0을 보냄
  • j = 1일 때, 'h' >> (7 - 1)은 01, 01& 1은 0이므로 서버에 1을 보냄
  • j = 2일 때, 'h' >> (7 - 2)은 011, 011 & 1은 1이므로 서버에 1을 보냄
  • ...... 이 과정을 j = 7 될때까지 복

 

 

8.2. server

if (sig == SIGUSR1)
{
	tmp | 0;
    if (cnt < 7)
    	tmp << 1;
}
if (sig == SIGUSR2)
{
	tmp | 1;
    if (cnt < 7)
    	tmp << 1;
}

client에서 h에 대해서 8번을 보냈다면 server에서는 8번 보낸 것을 하나로 합쳐주는 과정이다

  • cnt = 0일 때, tmp = 0, sig = 0, tmp | 0 = 0, tmp << 1 = 00
  • cnt = 1일 때, tmp = 00, sig = 1, tmp | 1 = 01, tmp << 1 = 010
  • cnt = 2일 때, tmp = 010, sig = 1, tmp | 1 = 011, tmp << 1= 0110
  • .... 이 과정을 cnt가 7이 될때까지 반복

만일 cnt =7이되면 더이상 시프트 연산 없이 tmp을 출력하면 client에서 보내고자 했던 h가 출력이 된다

'42 Seoul' 카테고리의 다른 글

[42 Seoul] minishell  (0) 2023.03.17
[42 Seoul] push swap  (0) 2023.02.24
[42 Seoul] so_long  (0) 2022.11.17
[42 Seoul] get_next_line  (0) 2022.09.22
[42 Seoul] Born2beroot - Bonus  (0) 2022.09.06
복사했습니다!