프로세스는 뭘까? - 정의
리눅스 시스템 메모리에서 실행 중인 프로그램이라고 한다.
다수의 프로세스를 실시간으로 사용하는 기법을 멀티프로세싱이라 하며,
같은 시간에 여러 프로그램을 실행하는 방식을 멀티태스킹이라고 한다.
리눅스 시스템에서 프로세스가 어떻게 동작을 하는 지 의문이 생긴다.
- 프로세스가 실행을 대기한다면 실행할 때 어떤 과정을 거칠까?
- 프로세스는 어떤 구조체로 식별할까?
리눅스에는 프로세스를 관리하는 자료구조이자 객체를 “태스크 디스크립터”(Task Descriptor)라고 하며,
tast_struct 구조체로 표현된다.
이 구조체에서는 메모리 리소스, 프로세스 이름, 실행 시각, 프로세스 아이디, 프로세스 스택의 최상단 주소와 같은 속성 정보가 저장된다.
프로세스의 실행 흐름을 표현하는 또 한 가지의 중요한 공간은 프로세스 스택 공간이며, 이 프로세스 스택의 최상단 주소에 thread_info 구조체가 있다.
프로세스는 함수를 호출하면서 함수를 실행한다. 그렇다면 함수를 호출하고 실행할 때 “프로세스 스택의 메모리 공간”을 사용한다. 모든 프로세스들은 커널 공간에서 실행할 때 각자 스택 공간을 할당받으며 스택 공간 내에서 함수를 실행한다.
리눅스 커널에서 프로세스를 표현할 수 있는 다음과 같은 자료가 있다고 정리할 수 있다.
- task_struct 구조체 : 태스크 디스크립터
- thread_info 구조체 : 프로세스 스레드 정보
스레드
스레드는 유저 레벨에서 생성된 가벼운 프로세스이다.
일반 프로세스에 비해 컨텍스트 스위칭을 수행할 때 시간이 적게 걸린다.
자신이 속한 프로세스 내의 다른 스레드와 파일 디스크립터, 파일 및 시그널 정보에 대한 주소 공간을 공유한다.
그러나 커널 입장에서는 스레드를 다른 프로세스와 동등하게 관리한다.
태스크 디스크립터에서 스레드 그룹 여부를 점검할 뿐이다.
ps 명령어로 프로세스 확인
라즈베리파이에서 “ps -ely”를 입력하자.
root@raspberrypi:/home/pi/rpi_kernel_src/linux # ps -ely
S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD
S 0 1 0 0 80 0 7876 8420 do_epo ? 00:00:11 systemd
S 0 2 0 0 80 0 0 0 kthrea ? 00:00:01 kthreadd
I 0 3 2 0 60 -20 0 0 rescue ? 00:00:00 rcu_gp
I 0 4 2 0 60 -20 0 0 rescue ? 00:00:00 rcu_par_gp
I 0 8 2 0 60 -20 0 0 rescue ? 00:00:00 mm_percpu_wq
S 0 9 2 0 80 0 0 0 smpboo ? 00:00:02 ksoftirqd/0
I 0 10 2 0 80 0 0 0 rcu_gp ? 00:00:10 rcu_sched
I 0 11 2 0 80 0 0 0 rcu_gp ? 00:00:00 rcu_bh
S 0 12 2 0 -40 - 0 0 smpboo ? 00:00:00 migration/0
.....
이런 것을 보면서 ps 명령어는 리눅스 커널 내부의 어떤 자료구조를 접근해서 전체 프로세스 정보를 출력하는 걸까라는 궁금증이 생길 수 있다.
답변을 말하자면, init_task 전역 변수를 통해 전체 프로세스 목록을 출력한다.
리눅스 시스템에서 생성된 모든 프로세스는 init 프로세스를 표현하는 자료구조인 init_task 전역변수의 tasks 필드에 연결리스트로 등록된다.
이번에는 라즈베리파이에서 “ps -ejH”라고 입력하자.
PID PGID SID TTY TIME CMD
2 0 0 ? 00:00:01 kthreadd
3 0 0 ? 00:00:00 rcu_gp
4 0 0 ? 00:00:00 rcu_par_gp
8 0 0 ? 00:00:00 mm_percpu_wq
9 0 0 ? 00:00:02 ksoftirqd/0
10 0 0 ? 00:00:10 rcu_sched
11 0 0 ? 00:00:00 rcu_bh
12 0 0 ? 00:00:00 migration/0
13 0 0 ? 00:00:00 cpuhp/0
14 0 0 ? 00:00:00 cpuhp/1
15 0 0 ? 00:00:00 migration/1
16 0 0 ? 00:00:08 ksoftirqd/1
19 0 0 ? 00:00:00 cpuhp/2
20 0 0 ? 00:00:00 migration/2
21 0 0 ? 00:00:00 ksoftirqd/2
24 0 0 ? 00:00:00 cpuhp/3
25 0 0 ? 00:00:00 migration/3
26 0 0 ? 00:00:00 ksoftirqd/3
29 0 0 ? 00:00:00 kdevtmpfs
30 0 0 ? 00:00:00 netns
34 0 0 ? 00:00:00 khungtaskd
35 0 0 ? 00:00:00 oom_reaper
36 0 0 ? 00:00:00 writeback
37 0 0 ? 00:00:00 kcompactd0
38 0 0 ? 00:00:00 crypto
.....
731 731 731 ? 00:00:00 ssh-agent
808 807 807 ? 00:00:00 menu-cached
976 976 976 ? 00:00:00 sshd
8165 8165 8165 ? 00:00:00 sshd
8178 8165 8165 ? 00:00:00 sshd
8181 8181 8181 pts/0 00:00:00 bash
8195 8195 8181 pts/0 00:00:00 su
8200 8200 8181 pts/0 00:00:00 bash
8296 8296 8181 pts/0 00:00:00 ps
이 프로세스 목록은 부모 자식 프로세스 관계를 토대로 출력한 것이다.
pid가 2인 “kthreadd” 프로세스는 커널 공간에만 실행하는 커널 프로세스를 생성하는 임무를 수행한다.
kthreadd 아래에 존재하는 자식 프로세스를 커널 스레드, 커널 프로세스라고 하며, 커널 공간에서만 실행된다.
커널이 프로세스를 생성할 때는 프로세스에 고유의 정수형 ID값을 부여한다. 이를 PID라고 한다. pid_t라는 타입으로 <sys.types.h> 헤더 파일에 저장되어 있다.
커널은 어떤 기준으로 PID를 프로세스에게 부여할까요? PID를 1씩 증가시키면서 프로세스에게 PID를 부여한다.
리눅스 시스템마다 생성되는 프로세스는 대부분 다르다. 하지만 리눅스에서 공통으로 커널이 생성하는 프로세스가 있는데 각각 다음과 같은 PID를 부여한다.
- swapper 프로세스 : 0
- init 프로세스 : 1
- kthreadd 프로세스 : 2
일반 유저 레벨에서 어떻게 확인할 수 있을까요? ( 상위 어플리케이션 입장에서 ) getpid()함수를 사용해서 사용이 가능합니다. 그러면 getpid() 함수를 호출하면 커널에서는 어떤 함수가 호출 될까요?
/linux/kernel/sys.c
879 /**
880 * sys_getpid - return the thread group id of the current process
881 *
882 * Note, despite the name, this returns the tgid not the pid. The tgid and
883 * the pid are identical unless CLONE_THREAD was specified on clone() in
884 * which case the tgid is the same in all threads of the same group.
885 *
886 * This is SMP safe as current->tgid does not change.
887 */
888 SYSCALL_DEFINE0(getpid)
889 {
890 return task_tgid_vnr(current);
891 }
892
task_tgid_vnr 함수에 현재 실행 중인 프로세스의 태스크 디스크립터 주소가 담긴 current를 인자를 호출해 PID를 읽는다.