04. process

  • Haram Lee
  • 2026-04-14
  • studies / 26-1 / operating-systems

Process

Process Creation

Cooperative multitasking

  • A 실행
  • A가 yield() 호출
  • kernel로 들어감
  • 스케줄러가 다음 B를 실행시킴
  • B가 yield() 호출
  • 다시 OS가 개입하여 A 실행
  • 즉, 프로세스의 협조 하에 CPU가 다른 프로세스로 넘어감. 문제는 A가 yield()를 안 하면 B는 실행하지 못할 수도 있다.

True multitasking

  • A 실행
  • timer IRQ 발생
  • kernel로 들어감
  • 스케줄러가 B로 바꿈
  • B 실행
  • time IRQ 발생
  • 다시 OS가 개입하여 A 혹은 다른 프로세스를 선택

Example

  • A 실행중
  • A가 read() 호출
  • kernel로 들어감
  • kernel이 I/O 요청을 보냄
  • 디스크는 느리기 때문에 바로 결과가 오지 않음 > A가 blocked가 됨
  • CPU를 놀게 할 수 없으니 OS가 B를 실행시킴
  • IRQ(I/O complete)가 들어오고 ready 상태로 돌아올 수 있게 됨
  • B가 돌다가 yield() 실행, OS가 context switch를 하고 다시 A를 실행시킨다

Process State Transitions

  • Created → Ready
  • Ready → Running
  • Running → Ready
  • Running → Blocked
  • Blocked → Ready
  • Running → exit

Implementing Processes: PCB

  • PCB(Process Control Block / Process Descriptor)
    • CPU registers
    • PID, PPID, process group, priority, process state, signals
    • CPU scheduling information
    • Memory management information
    • Accounting information
    • File management information
    • I/O status information
    • Credentials

Context switch

  • CPU를 한 프로세스에서 다른 프로세스로 바꾸는 행위.
    • A가 실행 중이었음
    • 타이머 인터럽트가 들어옴
    • 이제 B를 실행하기로 결정함
    • A의 현재 레지스터 값 저장
    • A의 실행 상태를 PCB 같은 곳에 기록
    • B의 저장된 레지스터 값 복원
    • CPU가 이제 B를 실행
  • overhead
    • administrative overhead = 실제 사용자 프로그램이 아니라, OS가 프로그램을 바꾸기 위해 드는 비용. 레지스터와 메모리 맵 저장/복원, 메모리 캐시 flush/reload, 여러가지 테이블과 리스트 업데이트 등이 필요하다.

Process APIs

C
#include <sys/types.h>
#include <unistd.h>

int main()
{
	int pid;
	
	if ((pid = fork()) == 0)
	/* child: 부모 프로세스에서는 fork()가 0이 아닌 값, 즉 자식의 PID를 반환 */
	printf("Child of %d is %d\n"), getppid(), getpid());
	else
		/* parent: 자식 프로세스에서는 fork()가 0을 반환*/
		printf("I am %d. My child is %d\n", getpid(), pid);
	return 0;
}
C
% ./a.out
I am 31098. My child is 31099.
Child of 31098 is 31099.

% ./a.out
Child of 31100 is 31101.
I am 31100. My child is 31101.
  • fork() 이후 동시에 runnable 상태가 되고, CPU를 누가 먼저 스케줄받느냐에 따라 출력 순서가 달라짐
  • 부모/자식 둘 다 실행되며, 출력 순서는 결정되어 있지 않다. 따라서 동기화하지 않으면 실행 순서에 의존하면 안 된다.

Shell

  • job control system; 프로그래머가 여러 프로그램을 실행하고 관리하게 해 주는 도구.

shell이 user-level에서 도는데, 각 프로그램을 실행하려면 어떤 system call이 필요할까?

  • 프로세스 생성 필요
  • 새 프로그램 적재 필요
  • 파일에 쓰기 필요
  • 필요하면 부모가 자식 기다리기 필요
    • 즉 shell은 내부적으로 fork(), exec(), wait() 같은 걸 써야 한다.

UNIX Process Management APIs

  • fork()
    • 부모를 복제해서 새 프로세스를 만든다. 부모가 가진 자원가 권한을 자식이 이어받는다. 자식은 부모 주소의 공간도 복제한다.
    • 부모는 wait()으로 자식을 기다리거나, 병렬로 실행될 수도 있다.
  • exec()
    • 현재 프로세스 이미지를 새 프로그램으로 교체한다. 새 프로세스를 만들지 않고 단순히 다른 프로그램으로 전환한다.
  • exit(status)
  • wait(&status

Process Termination

  1. Normal exit (voluntary)
    • return 0
    • exit(0)
  2. Error exit (voluntary)
    • return non-zero
    • exit(non-zero)
  3. Fatal error (involuntary)
    • segmentation fault
    • protection fault
    • 자원 한도 초과 등
  4. Killed by another process (involuntary)
    • 다른 프로세스가 signal을 보내서 종료되는 경우.
  • zombie process
    • 프로세스는 이미 종료됐는데, 부모가 아직 wait()로 회수하지 않아서 PCB 같은 종료 정보가 남아 있는 상태.
  • exit()
  • kill()
  • wait()

Simplified Shell

C
int main(void)
{
    char cmdline[MAXLINE];
    char *argv[MAXARGS];
    pid_t pid;
    int status;

    while (getcmd(cmdline, sizeof(buf)) >= 0) {
        parsecmd(cmdline, argv);

        if (!builtin_command(argv)) {
            if ((pid = fork()) == 0) {
                if (execv(argv[0], argv) < 0) {
                    printf("%s: command not found\n", argv[0]);
                    exit(0);
                }
            }
            waitpid(pid, &status, 0);
        }
    }
}

xv6: PCB

  • xv6에서는 PCB 역할을 하는 struct procproc.h에 있다

Performing Context Switch

  • A 실행 중
  • timer interrupt 발생
  • 하드웨어가 A의 레지스터를 A의 kernel stack에 저장
  • CPU가 kernel mode로 전환
  • trap handler로 점프
  • 커널이 trap 처리
  • switch 루틴 호출
  • A의 커널 문맥을 PCB(A)에 저장
  • B의 커널 문맥을 PCB(B)에서 복원
  • B의 kernel stack으로 전환
  • return-from-trap
  • 하드웨어가 B의 사용자 문맥 복원
  • user mode로 돌아감
  • B의 IP로 점프해서 B 실행 재개

Handling Interrupts

  • disk interrupt
  • regs(A) 저장
  • kernel mode 전환
  • trap handler 진입
  • IRQ 처리
  • return-from-trap
  • regs(A) 복원
  • user mode 복귀
  • A 계속 실행

Handling System call

  • trap 발생
  • 레지스터 저장
  • kernel mode로 진입
  • trap handler
  • syscall handler 호출
  • 처리 후 return-from-trap
  • 다시 A로 복귀

xv6: Context Switch

  • struct trapframe

    • kernel mode 진입할 때 저장되는 사용자 상태
    • 즉 user registers 쪽
  • struct context

    • 실제 context switch 때 저장되는 kernel-level 상태
    • scheduler 전환용

Process State Queues

  • Ready queue (run queue)
    • 지금 당장 실행 가능하고 CPU만 기다리는 프로세스들.
  • Wait queue(s)
    • 각 이벤트 종류별로 따로 존재.
      • disk I/O queue
      • keyboard I/O queue
      • mutex wait queue
      • timer/message wait queue
  • scheduled 되면 ready queue에서 CPU로 감
  • time slice expired면 다시 ready queue
  • disk I/O 요청하면 disk queue
  • keyboard 기다리면 keyboard queue
  • mutex 기다리면 mutex wait queue
  • interrupt/event completion이 오면 다시 ready queue
  • exit하면 시스템에서 제거 방향으로 감

xv6: Disk I/O Queue

  • 프로세스가 디스크 읽기 요청
  • 바로 끝나지 않으면 wait queue로 block
  • 디스크 인터럽트가 오면 커널이 완료 확인
  • 해당 프로세스를 깨워 ready queue로 이동

xv6: Virtual Memory Layout

  • uvm (user virtual memory)

    • allocuvm()
    • copyuvm()
    • loaduvm()
    • switchuvm()
  • kvm (kernel virtual memory)

    • setupkvm()
    • switchkvm()

fork()

  1. 새 PCB 생성/초기화
  2. 새 주소 공간 생성/초기화
  3. 부모 주소 공간 전체를 복사
  4. 열린 파일 같은 커널 자원을 부모와 연결
  5. 새 PCB를 ready queue에 넣기
  6. 부모에겐 child PID 반환, 자식에겐 0 반환

exec()

  1. 현재 프로세스 주소 공간을 지우고 대부분 자원 해제
  2. 새 프로그램 prog를 현재 프로세스 주소 공간에 적재
  3. 새 프로그램용 하드웨어 context와 인자 초기화
  4. exec()는 새 프로세스를 만들지 않는다
  5. exec()가 반환한다는 건 무슨 뜻일까?” → 보통 실패했다는 뜻

IPC

  1. Shared memory: 같은 메모리 영역을 함께 접근
  2. Message passing: 메시지를 주고받는 방식
Discussion