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()같은 걸 써야 한다.
- 즉 shell은 내부적으로
UNIX Process Management APIs
fork()- 부모를 복제해서 새 프로세스를 만든다. 부모가 가진 자원가 권한을 자식이 이어받는다. 자식은 부모 주소의 공간도 복제한다.
- 부모는 wait()으로 자식을 기다리거나, 병렬로 실행될 수도 있다.
exec()- 현재 프로세스 이미지를 새 프로그램으로 교체한다. 새 프로세스를 만들지 않고 단순히 다른 프로그램으로 전환한다.
exit(status)wait(&status
Process Termination
- Normal exit (voluntary)
return 0exit(0)
- Error exit (voluntary)
return non-zeroexit(non-zero)
- Fatal error (involuntary)
- segmentation fault
- protection fault
- 자원 한도 초과 등
- 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 proc가proc.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()
- 새 PCB 생성/초기화
- 새 주소 공간 생성/초기화
- 부모 주소 공간 전체를 복사
- 열린 파일 같은 커널 자원을 부모와 연결
- 새 PCB를 ready queue에 넣기
- 부모에겐 child PID 반환, 자식에겐 0 반환
exec()
- 현재 프로세스 주소 공간을 지우고 대부분 자원 해제
- 새 프로그램
prog를 현재 프로세스 주소 공간에 적재 - 새 프로그램용 하드웨어 context와 인자 초기화
exec()는 새 프로세스를 만들지 않는다- “
exec()가 반환한다는 건 무슨 뜻일까?” → 보통 실패했다는 뜻
IPC
- Shared memory: 같은 메모리 영역을 함께 접근
- Message passing: 메시지를 주고받는 방식


