댓글 (0)
0/2000

[유저 모드]
1. 사용자가 "ls -l" 입력
2. 쉘이 명령어 파싱
3. 외부 명령어임을 확인
[시스템 콜 → 커널 모드]
4. fork() 호출
- 커널: 자식 프로세스 생성
- 메모리 할당, PCB 생성
[유저 모드 복귀]
5. 부모와 자식 모두 fork() 이후 실행
[자식 프로세스 - 시스템 콜 → 커널 모드]
6. exec() 호출
- 커널: 메모리를 ls 프로그램으로 교체
- 프로그램 로드 및 시작
[유저 모드 복귀]
7. ls 프로그램 실행
[시스템 콜 → 커널 모드 (반복)]
8. open(), read(), stat(), write() 등
- 커널: 파일 시스템 접근
- 데이터 읽기 및 출력
[시스템 콜 → 커널 모드]
9. exit() 호출
- 커널: 자원 해제, 종료 상태 보관
[부모 프로세스 - 시스템 콜에서 복귀]
10. wait() 완료
- 자식 종료 감지
- 좀비 프로세스 정리
[유저 모드]
11. 쉘이 새로운 프롬프트 표시
12. 다음 명령어 대기사용자 명령어 입력
username@hostname:~$ ls -l /home이 시점까지는 모든 작업이 유저 모드에서 일어난다.
유저 모드의 특징
쉘의 명령어 처리 준비
내장 명령어 (Built-in Commands)
쉘 자체에 구현되어 있는 명령어로, 쉘이 직접 처리한다.
예시
cd: 디렉터리 변경exit: 쉘 종료export: 환경 변수 설정alias: 별칭 설정source (또는 .): 스크립트 실행처리 방식
// 의사 코드
if (is_builtin_command(command)) {
execute_builtin(command, args); // 쉘이 직접 실행
return; // fork/exec 불필요
}외부 명령어 (External Commands)
별도의 실행 파일로 존재하는 프로그램으로, 새로운 프로세스를 생성해야 한다.
예시
ls: 디렉터리 내용 표시 (/bin/ls)cat: 파일 내용 출력 (/bin/cat)grep: 텍스트 검색 (/bin/grep)실행 파일 위치 찾기
# PATH 환경 변수에서 검색
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# 각 디렉터리에서 "ls" 찾기
/usr/local/bin/ls (없음)
/usr/bin/ls (없음)
/bin/ls (찾음!)프로세스 생성 및 실행 과정
// 쉘의 명령어 실행 의사 코드
pid_t pid = fork(); // 시스템 콜 1: 프로세스 생성
if (pid < 0) {
// fork 실패
perror("fork failed");
} else if (pid == 0) {
// 자식 프로세스
execve("/bin/ls", args, env); // 시스템 콜 2: 프로그램 실행
perror("exec failed"); // exec 실패 시에만 도달
exit(1);
} else {
// 부모 프로세스 (쉘)
int status;
waitpid(pid, &status, 0); // 시스템 콜 3: 자식 종료 대기
// 자식이 종료되면 여기서 계속 진행
}시스템 콜이란?
유저 모드에서 커널 모드로 진입하는 인터페이스다. 사용자 프로그램이 운영체제의 서비스를 요청하는 방법이다.
모드 전환 과정
fork()의 역할
현재 프로세스(부모)를 복제하여 새로운 프로세스(자식)를 생성한다.
커널에서의 작업
1) 새로운 프로세스 생성
2) 메모리 할당
3) 자원 복사
4) 부모-자식 관계 설정
fork() 반환값
pid_t pid = fork();
// 부모 프로세스에서: pid > 0 (자식의 PID)
// 자식 프로세스에서: pid == 0
// 실패 시: pid < 0fork() 호출 후, 부모와 자식 모두 fork() 다음 줄부터 실행된다.
wait() 시스템 콜 - 부모의 대기
부모 프로세스(쉘)는 자식이 종료되기를 기다린다.
// 부모 프로세스 (쉘)
int status;
pid_t result = waitpid(pid, &status, 0); // 시스템 콜
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
// exit_code == 0: 정상 종료
// exit_code != 0: 오류 발생
}wait()의 역할
쉘로 제어권 복귀
# 자식 프로세스 종료 후
username@hostname:~$ _ # 커서가 다시 프롬프트에 표시쉘의 동작
wait() 시스템 콜에서 복귀커널에서의 작업
1) 실행 파일 검증
/bin/ls 파일이 존재하는지 확인2) 메모리 공간 교체
기존 프로세스 메모리:
┌─────────────┐
│ bash 코드 │ ← 쉘의 코드
├─────────────┤
│ bash 데이터 │ ← 쉘의 데이터
├─────────────┤
│ 힙 │
├─────────────┤
│ 스택 │
└─────────────┘
exec() 실행 후:
┌─────────────┐
│ ls 코드 │ ← ls 프로그램의 코드
├─────────────┤
│ ls 데이터 │ ← ls 프로그램의 데이터
├─────────────┤
│ 힙 │ ← 새로 초기화
├─────────────┤
│ 스택 │ ← 새로 초기화
└─────────────┘3) 프로그램 로드
4) 프로그램 시작
main() 함수)으로 이동exec() 계열 함수들
// 다양한 형태의 exec 함수
execl("/bin/ls", "ls", "-l", NULL);
execv("/bin/ls", argv);
execve("/bin/ls", argv, envp); // 가장 기본적인 형태
execlp("ls", "ls", "-l", NULL); // PATH에서 검색exec()가 성공하면, 이후의 코드는 절대 실행되지 않는다. 프로세스가 완전히 새 프로그램으로 바뀌기 때문이다.
파일 시스템 접근
ls 프로그램이 실행되면, 디렉터리 내용을 읽기 위해 추가 시스템 콜을 사용한다.
// ls 프로그램의 내부 동작 (단순화)
// 1. 디렉터리 열기
int fd = open("/home", O_RDONLY | O_DIRECTORY); // 시스템 콜
if (fd < 0) {
perror("open failed");
exit(1);
}
// 2. 디렉터리 내용 읽기
struct dirent *entry;
DIR *dir = fdopendir(fd); // 시스템 콜
while ((entry = readdir(dir)) != NULL) { // 시스템 콜
// 각 파일/디렉터리 정보 가져오기
struct stat file_stat;
stat(entry->d_name, &file_stat); // 시스템 콜
// 파일 정보 출력
printf("%s\n", entry->d_name); // write 시스템 콜 내부 사용
}
// 3. 디렉터리 닫기
closedir(dir); // 시스템 콜주요 시스템 콜들
파일 관련
open(): 파일 열기read(): 파일 읽기write(): 파일 쓰기close(): 파일 닫기stat(): 파일 정보 가져오기디렉터리 관련
opendir(): 디렉터리 열기readdir(): 디렉터리 항목 읽기closedir(): 디렉터리 닫기출력 관련
write(): 표준 출력으로 데이터 쓰기write(STDOUT_FILENO, "hello\n", 6);커널의 역할
1) 권한 검사
2) 파일 시스템 접근
3) 자원 관리
명령 종료
ls 프로그램이 작업을 완료하면:
// ls 프로그램의 마지막
exit(0); // 시스템 콜: 정상 종료
// 또는
exit(1); // 시스템 콜: 오류 종료exit() 시스템 콜
커널의 처리
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!