1. 프로세스 (Process)
1.1 프로세스란?
- 리눅스 시스템 메모리에서 실행 중인 프로그램을 말함
- 스케줄링 대상인 태스크와 유사한 의미로 사용됨
- 다수의 프로세스를 실시간으로 사용하는 기법을 멀티프로세싱
- 같은 시간에 여러 프로그램을 실행시키는 기법을 멀티태스킹이라고 함
ex) 핸드론으로 전화하면서 메모하기 또는 음악을 들으며 웹 서치하기
즉, 여러 애플리케이션이 동시에 실행됨
이것은 멀티태스킹을 통해 시분할 방식으로 처리하기 때문에 가능
1.2 Task Discriptor
프로세스를 관리하는 자료구조, [태스크 디스크립터]
이 구조체는 프로세스가 쓰는 메모리 리소스, 프로세스 이름, 실행 시각, 프로세스 아이디(PID), 프로세스 스택의 최상단 주소와 같은 속성 정보가 저장되어 있다.
1.2.2 thread_info
프로세스의 실행 흐름을 표현하는 또 한가지 중요한 공간은 프로세스 스택 공간이며,
이 프로세스 스택의 최상단 주소에 thread_info 구조체가 있다.
kernel에서 process를 표현할 수 있는 자료구조는
1) task_struct 구조체 : 태스크 디스크립터
2) thread_info 구조체 : 프로세스 스레드 정보
프로세스가 무엇인지 알기 위해서는, 프로세스를 제어하는 함수들은 위 구조체 중심으로 실행되기에 두 자료구조(구조체)를 잘 알아야 한다.
2. 프로세스 실습
2.1 프로세스 정보 확인 : $ ps
Command : info ps
ps 명령어는 리눅스 커널 내부의 init_task 라는 전역변수를 통해 전체 프로세스 목록(user Level, kernel thread)을 출력한다.
init_task는 tasks 필드의 연결리스트로 등록 되어있어, 연결리스트를 순회하며 프로세스 정보인 task_struct 구조체의 주소를 계산해 프로세스 정보를 출력한다.
task : 말 그대로 ‘실행’의 의미를 뜻하나, 리눅스 커널의 함수 이름이나 변수 중에 task란 단어가 보이면 프로세스 관련 코드라 생각해도 좋다.
프로세스의 실행흐름을 표현하는 중요한 공간은 프로세스 스택 공간이며, 이 프로세스 스택의 최상단 주소에 thread_info가 있음
-process 확인하기
보이는 것과 같이 ps 명령어 또한 하나의 프로세스로 취급되는 것을 확인 할 수 있다.
2.2 $ ps -ejH 옵션 [Process의 Parent-child 관계]
이 옵션(-ejH)은 부모 자식 프로세스의 관계를 토대로 프로세스를 출력한다.
systemd : PID가 1인 모든 프로세스들을 관리하는 init 시스템 및 프로세스, 모든 유저공간에서 생성된 프로세스의 부모 프로세스 역할을 수행한다.
대부분의 리눅스 배포판에서는 PID가 1인 프로세스를 init 라고 부른다.
각 프로세스는 저마다 부모 자식 프로세스가 있다. 자식 프로세스가 종료될 때 부모 프로세스에게 신호를 알린다.
만약 부모 프로세스가 자식보다 먼저 종료된다면, 대부분 init 프로세스가 새로운 부모 프로세스의 역할을 수행한다.
2.3 getpid() function : 유저 공간에서의 PID 확인
위의 커널이 공통적으로 생성하는 프로세스 외에, 다른 일반 프로세스의 PID는 User공간에서 어떻게 확인할 수 있을까 ?
getpid() 함수를 호출하면 이에 대응하는 시스템 콜 핸들러인 sys_getpid() 함수가 호출된다.
[ftrace 에서 프로세스 확인하기]
프로세스 이름 : Hvdcp_opti
PID : 1550
위를 보면 알 수 있듯이 ftrace 메시지의 왼쪽 부분 정보는 pid가 1550인 Hvdcp_opti 프로세스가 실행 중이라는 사실을 말해준다.
3. 프로세스가 생성되는 과정
3.1 프로세스가 생성되는 과정
프로세스가 부모 프로세스로부터 어떻게 복제 되는가 ?
생성된 프로세스가 어떻게 실행을 시작하는가 ?
프로세스 자료구조는 어떻게 처리하는가?
3.2 리눅스에서 구동되는 프로세스
1) 유저 레벨에서 생성된 프로세스
유저 공간에서 프로세스를 생성하는 라이브러리의 도움을 받아 커널에게 프로세스 생성 요청을 한다.
2) 커널 레벨에서 생성된 프로세스
커널 내부의 kthread_create() 함수를 호출해서 커널 프로세스를 생성한다.
프로세스 종류에 따라 프로세스를 생성하는 흐름은 다르다. 하지만 프로세스를 생성할 때 _do_fork() 함수를 호출하는 공통점이 있다.
※ linux kernel ver. 5.4 이후 _do_fork() 함수 대신 kernel clone 함수를 사용한다
3.3 kernel_clone 함수
리눅스에서 구동 중인 모든 프로세스는 생성 시 kernel_clone 함수를 사용해 생성함
리눅스에서 프로세스 생성을 전담하는 프로세스
1) init
부팅 과정에서 유저 프로세스를 생성하는 역할
2) kthread
커널 레벨 프로세스를 생성
프로세스는 생성이 아닌 복제라고 한다. 프로세스 생성 시 ‘코드의 성능이나 속도를 개선’하기 위해 이미 생성된 프로세스에게서 리소스를 물려받는다. 즉, 프로세스를 생성할 때 부모 프로세스의 자료구조(데이터)를 복사한다.
3.4 _do_fork() 함수인자
Unsigned long Clone_flags : 부모 프로세스로부터 복제된 리소스의 정보
unsigned long stack_start : 유저영역 프로세스 생성 시 복사하려는 stack 주소
unsigned long stack_size : 유저영역에서 실행중인 stack의 크기
int __user *parent_tidptr,
int __user *child_tidptr
부모와 자식 스레드를 관리하는 핸들러
3.5 kernel에서 _do_fork() 호출
커널에서 프로세스를 생성할 때 프로세스 유형에 따라 _do_fork()함수를 호출하는 흐름은 다르다.
1) 유저모드에서 생성한 프로세스
sys_clone() 시스템 콜 핸들러 함수
2) 커널 모드에서 생성한 커널 스레드
kernel_thread() 함수
이를 각각 유저 레벨 프로세스 , 커널 레벨 프로세스라고 부른다.
3.6 유저모드와 커널모드는 나누는 기준
메모리 접근과 실행 권한으로 두 모드로 분류한다.
Q. 그렇다면 유저단에서 실행되는 프로세스들이 커널단의 데이터가 필요하다면 어떻게 요청할까 ?
A. 시스템 콜을 통해 특정 서비스를 커널에게 요청한다. 유저 모드에서는 커널 코드를 직접 실행하지 못하기 때문이다.
3.7 kernel mode에서 시스템 콜을 통한 서비스 처리
유저 모드에서 시스템 콜을 통해 서비스를 요청하면, 커널 모드에서는 시스템 콜에 대한 처리를 해줘야한다.
< 시스템콜에 대한 처리 과정 >
1. 시스템 콜에 전달한 인자의 오류 점검
2. 커널 내부 함수 호출
3. 유저에게 요청한 정보를 알려줌
4. 유저 프로세스
4.1 유저 프로세스
[유저모드에 해당하는 파일]
/user/lib/x86_64-linux-gnu
위와 같은 디렉터리, 파일들이 리눅스 시스템 구동에 필요한 라이브러리가 있다.
이 라이브러리 파일이 유저모드 코드라고 볼 수 있다.
리눅스에서 실행 중인 유저 application은 라이브러리와 링킹되어 메모리에 적대돼 실행되기 때문이다.
user 에서 do_fork() 호출
유저모드에서는 스스로 프로세스를 생성하지 못한다.
대신 리눅스에서 제공하는 라이브러리의 도움으로 프로세스 생성 요청이 가능하다.
커널 입장에서는 유저 레벨 프로세스도 동등한 프로세스로 간주한다.
4.2 유저 프로세스와 커널 프로세스의 차이
실행 출발점이 다르다.
1) 유저 레벨 프로세스
유저모드에서 fork()함수나 pthread_creat ()함수를 호출하고
위와 같은 파일의 도움으로 커널에 서비스를 요청해 생성한다.
2) 커널 레벨 프로세스
커널의 kthread_create()함수를 호출해 프로세스를 생성한다.
4.3 유저 프로세스를 생서앟ㄹ 때 _do_fork 함수 처리흐름
<유저 레벨 프로세스의 흐름 생성도>
1. 유저 공간에서 fork()함수를 호출하면 리눅스에서 제공하는 라이브러리의 도움을 받아 커널에게 프로세스 생성을 요청한다.
2. 리눅스에서 제공하는 라이브러리 코드가 실행되며 시스템 콜을 발생시킨다.
3. 리눅스 커널 계층에서는 fork() 함수에 대응하는 시스템 콜 핸들러인 sys_clone() 함수를 호출한다.
4.4 sys_clone 함수 분석
LINUX/android/kernel/msm-4.9/kernel/fork.c
*커널에서 제공하는 SYSCALL_DEFINE5 매크로와 함께 함수 이름을 지정하면,
커널 소스를 빌드하는 과정에서 지정한 함수 이름 앞에 ‘sys_’ 접두사를 붙여 심벌을 생성한다.
즉, 위와 같이 시스템 콜 함수를 정의하면 sys_clone() 시스템 콜 함수가 생성되는 것이다.
코드에서와 같이 sys_clone()함수는 _do_fork() 함수를 그대로 호출한다.
유저 레벨 프로세스를 생성할 때 처리 흐름은 다음과 같이 정리할 수 있다.
Ⅰ. 유저공간에서 fork() 함수를 호출하며 시스템 콜을 발생시킴
Ⅱ. 커널 공간에서 sys_clone() 함수를 호출
Ⅲ. Sys_clone() 함수는 _do_fork() 함수를 호출해 프로세스를 생성
4.4 sys_clone과 sys_vfork() 함수 분석
Sys_vfork() 함수는 시스템 콜 함수로 리눅스 커널 버전에서 썼던 레거시(과거) 코드이다.
Sys_vfork()함수 또한 sys_clone()함수와 마찬가지로 do_fork()함수를 그대로 호출하는 것을 알 수 있다.
5. 커널 프로세스
5.1 커널 프로세스란?
시스템 콜 없이 커널 함수로 생성되어 커널 공간에서만 실행되는 프로세스를 의미한다.
예) 커널 스레드 : 커널 공간에서 시스템 리소스(메모리, 전원) 관리를 수행
5.2 커널 스레드 생성과정
<커널 스레드의 생성 흐름도>
커널 스레드의 생성과정은 2단계로 나눌 수 있다.
1단계 : kthreadd 프로세스에게 커널 프로세스 생성을 요청
kthread_create함수를 호출해 kthreadd 프로세스에게 커널 프로세스 생성을 요청한다. 그리고 kthreadd 프로세스를 깨운다.
2단계 : 커널 프로세스 생성
kthreadd 프로세스는 깨어나 자신에게 커널 프로세스 생성 요청을 했는지 점검한다. 프로세스를 행성해달라는 요청이 있다면, 프로세스를 생성한다.
5.3 커널 스레드 생성
kthread_create() 함수를 실행하면 커널 스레드를 생성할 수 있다.
kthread_creage() 함수를 호출하면 커널 스레드를 생성하는 kthreadd 프로세스에게 커널 스레드 생성 요청을 한다.
이후 kthreadd스레드는 _do_fork() 함수를 실행해 프로세스를 생성한다.
커널 스레드는 언제 실행을 시작할까 ?
커널은 시스템 부팅과정에서 대부분의 커널 스레드를 생성한다.
생성 된 후 바로 일을 시작하며, 이후 background에서 주기적으로 실행된다.
하지만, 모든 커널 스레드가 부팅과정에서만 생성되는 것은 아니다. 커널이 동적으로 커널 스레드가 필요하다고 판단할 때 커널 스레드를 생성할 때도 있다.
커널이 동적으로 커널 스레드를 생성하는 예
1. 리눅스 드라이버에서 많은 워크를 워크큐에 큐잉하면 커널은 커널 스레드의 한 종류인 워커 스레드를 생성한다.
2. 커널에서 메모리가 부족하면 페이지를 확보하는 일을 하는 kswapd 스레드를 깨워 실행한다.
보통 리눅스 커널 시스템이 더 많은 일을 해야 할 때 커널 스레드를 생성한다.
'Embedded : : Linux > : : Linux Kernel' 카테고리의 다른 글
커널 스레드 (0) | 2024.10.12 |
---|---|
do_fork() 함수, 그리고 5.x 이후 (0) | 2024.10.12 |
[crash utility] Makefile Error: gdb_merge (0) | 2023.11.14 |
Kernel이 하는 일 (0) | 2023.11.08 |
Linux : Build System (Makefile, CMake) (0) | 2023.07.14 |