You are looking for information, articles, knowledge about the topic nail salons open on sunday near me 리눅스 프로세스 생성 과정 on Google, you do not find the information you need! Here are the best content compiled and compiled by the https://chewathai27.com/to team, along with other related topics such as: 리눅스 프로세스 생성 과정 Fork 함수 개념, 리눅스 좀비 프로세스 만들기
fork() 는 프로세스 안에서 fork 함수가 실행되면, fork 함수를 가지고 있던 프로세스(부모)를 복사해 자식 프로세스를 생성한다. exec() 는 프로그램 실행파일을 읽어 현재 프로세스의 TEXT, DATA, BSS 영역을 새로운 프로세스의 이미지로 덮어 씌운다.
리눅스 프로세스 생성하기
- Article author: velog.io
- Reviews from users: 36130 Ratings
- Top rated: 3.2
- Lowest rated: 1
- Summary of article content: Articles about 리눅스 프로세스 생성하기 Updating …
- Most searched keywords: Whether you are looking for 리눅스 프로세스 생성하기 Updating 리눅스에서 프로세스를 생성하는 과정을 알아보기 전에, 프로세스의 기본 구조를 다시 생각해보자.
프로세스의 기본 구조는 다음과 같다.
TEXT : 프로세스의 정의나 명령
DATA : 프로그램이 사용하는 정적 변수
BSS : 초기화하지 않은 전역 변수나 정적 변수
HE - Table of Contents:
Linux Kernel/Driver Hacks :: [리눅스커널][프로세스] 프로세스는 어떻게 생성하나?
- Article author: austindhkim.tistory.com
- Reviews from users: 37261 Ratings
- Top rated: 4.0
- Lowest rated: 1
- Summary of article content: Articles about Linux Kernel/Driver Hacks :: [리눅스커널][프로세스] 프로세스는 어떻게 생성하나? 유저 레벨 프로세스는 init 프로세스, 커널 레벨 프로세스(커널 스레드)는 kthreadd 프로세스가 생성하는 것입니다. 프로세스 생성 과정에 대해서 조금 더 … …
- Most searched keywords: Whether you are looking for Linux Kernel/Driver Hacks :: [리눅스커널][프로세스] 프로세스는 어떻게 생성하나? 유저 레벨 프로세스는 init 프로세스, 커널 레벨 프로세스(커널 스레드)는 kthreadd 프로세스가 생성하는 것입니다. 프로세스 생성 과정에 대해서 조금 더 … 프로세스에 대한 이해를 하려면 프로세스가 어떻게 생성되는 지 알면 좋습니다. 프로세스 생성 과정에서 프로세스를 관리하는 자료구조 관계를 알 수 있기 때문입니다. 리눅스에서 구동되는 프로세스는 크게 유저..
- Table of Contents:
REAKWON :: [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드
- Article author: reakwon.tistory.com
- Reviews from users: 14339 Ratings
- Top rated: 4.5
- Lowest rated: 1
- Summary of article content: Articles about REAKWON :: [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드 [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드. REAKWON 2018. 12. 10. 21:03. 프로세스(process). 프로세스는 간단히 말하자면 실행 중인 프로그램을 … …
- Most searched keywords: Whether you are looking for REAKWON :: [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드 [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드. REAKWON 2018. 12. 10. 21:03. 프로세스(process). 프로세스는 간단히 말하자면 실행 중인 프로그램을 … 프로세스(process) 프로세스는 간단히 말하자면 실행 중인 프로그램을 의미합니다. 아마 여러분들은 컴퓨터를 하면서 아주 빈번하게 듣는 용어이기도 합니다. 실행 중인 프로그램이라?? 컴퓨터에서 말하는 실행 중..
- Table of Contents:
티스토리툴바
〔LINUX/UNIX〕프로세스 생성과 실행: pid, fork, exec, pipe
- Article author: love-every-moment.tistory.com
- Reviews from users: 32468 Ratings
- Top rated: 4.1
- Lowest rated: 1
- Summary of article content: Articles about 〔LINUX/UNIX〕프로세스 생성과 실행: pid, fork, exec, pipe 어느 프로세스가 먼저 실행되는지는 알 수 없으며 유닉스 운영체제의 스케쥴링에 따라 처리 순서가 달라진다. fork() 함수 처리 과정: #1. fork() 함수 … …
- Most searched keywords: Whether you are looking for 〔LINUX/UNIX〕프로세스 생성과 실행: pid, fork, exec, pipe 어느 프로세스가 먼저 실행되는지는 알 수 없으며 유닉스 운영체제의 스케쥴링에 따라 처리 순서가 달라진다. fork() 함수 처리 과정: #1. fork() 함수 … 1. 프로세스(Process) (1) 프로세스란? 현재 실행 중인 프로그램 즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당 받아 실행중인 것 운영체제의 제어 아래 실행(Running), 대기(Waiting), 중..
- Table of Contents:
Love Every Moment
〔LINUXUNIX〕프로세스 생성과 실행 pid fork exec pipe 본문
티스토리툴바
[Linux Kernel] 커널 스레드(프로세스) 생성 과정 소스 분석
- Article author: darkengineer.tistory.com
- Reviews from users: 942 Ratings
- Top rated: 4.1
- Lowest rated: 1
- Summary of article content: Articles about [Linux Kernel] 커널 스레드(프로세스) 생성 과정 소스 분석 프로세스 생성 과정. 크게 두가지로 분류 할 수 있다. 1. 유저 레벨에서 생성된 프로세스. 라이브러리(GNU C : glibc) 의 도움 … …
- Most searched keywords: Whether you are looking for [Linux Kernel] 커널 스레드(프로세스) 생성 과정 소스 분석 프로세스 생성 과정. 크게 두가지로 분류 할 수 있다. 1. 유저 레벨에서 생성된 프로세스. 라이브러리(GNU C : glibc) 의 도움 … 다크엔지니어 님의 블로그입니다.
- Table of Contents:
커널 스레드 생성 과정 소스 분석
6. 프로세스 생성과 실행 · UNIXBasic
- Article author: jihooyim1.gitbooks.io
- Reviews from users: 31928 Ratings
- Top rated: 4.1
- Lowest rated: 1
- Summary of article content: Articles about 6. 프로세스 생성과 실행 · UNIXBasic 유닉스 운영체제의 스케쥴링(scheduling)에 따라 처리 순서가 달라진다. fork함수 실행시 처리 과정. fork 함수 호출; 새로운 프로세스(자식 프로세스) 생성; fork함수로 … …
- Most searched keywords: Whether you are looking for 6. 프로세스 생성과 실행 · UNIXBasic 유닉스 운영체제의 스케쥴링(scheduling)에 따라 처리 순서가 달라진다. fork함수 실행시 처리 과정. fork 함수 호출; 새로운 프로세스(자식 프로세스) 생성; fork함수로 …
- Table of Contents:
6-1개요
6-2프로세스 생성
6-3프로세스 종료
6-4 exec 함수군 활용
6-5 프로세스 동기화
[ Linux Kernel ] 06. Child Process 생성하기
- Article author: coder-in-war.tistory.com
- Reviews from users: 32775 Ratings
- Top rated: 3.4
- Lowest rated: 1
- Summary of article content: Articles about [ Linux Kernel ] 06. Child Process 생성하기 # Note 단순히 함수를 호출한 후 리턴해서 그 다음 실행흐름으로 위치했다는 의미다. 프로그램적으로 너무나도 당연한 과정이다. 그 후 ready queue 에 … …
- Most searched keywords: Whether you are looking for [ Linux Kernel ] 06. Child Process 생성하기 # Note 단순히 함수를 호출한 후 리턴해서 그 다음 실행흐름으로 위치했다는 의미다. 프로그램적으로 너무나도 당연한 과정이다. 그 후 ready queue 에 … 8. Child Process 생성하기 컴퓨터를 부팅하면 제일 먼저 커널 프로세스가 로드된다. 그리고 이 커널은 터미널이 켜질때 마다 그에 해당하는 Shell, 즉 Child Process를 만든다. Shell은 사용자의 입력을 기다리고..꾸준함이 무기입니다.
- Table of Contents:
81 Fork
티스토리툴바
프로세스의 생성과 종료
- Article author: boomrabbit.tistory.com
- Reviews from users: 22193 Ratings
- Top rated: 3.5
- Lowest rated: 1
- Summary of article content: Articles about 프로세스의 생성과 종료 이 p 값은 커널 내에 프로세스의 다양한 속성에 접근하기 위한 인덱스로 사용이 됩니다. 위에 그림은 리눅스 운영체제에서 프로세스들간의 형태를 … …
- Most searched keywords: Whether you are looking for 프로세스의 생성과 종료 이 p 값은 커널 내에 프로세스의 다양한 속성에 접근하기 위한 인덱스로 사용이 됩니다. 위에 그림은 리눅스 운영체제에서 프로세스들간의 형태를 … 정말 오랜만에 운영체제 포스팅을 하네요….ㅠㅠ 꾸준하게 한다는게 결코 쉽지 않다라는걸 다시 한번 느낍니다. 지난시간 우린 스케쥴링에 관해 공부해봤습니다. 단기,중기,장기 스케쥴러에 관해 공부해 봤죠. 이..
- Table of Contents:
순간을 성실히 화려함보단 꾸준함을
프로세스의 생성과 종료 본문
프로세스의 생성(process creation)
fork()
exec()
wait()
프로세스 종료(process termination)
[OS] 프로세스의 생성과 종료
- Article author: pongsoyun.tistory.com
- Reviews from users: 42759 Ratings
- Top rated: 3.4
- Lowest rated: 1
- Summary of article content: Articles about [OS] 프로세스의 생성과 종료 컴퓨터에서 실제 프로세스 생성과정을 보자. BOOTING : OS가 RAM 에 resent; OS가 가장 먼저 첫 process를 만든다. (ex. Linux: init ); 그 init … …
- Most searched keywords: Whether you are looking for [OS] 프로세스의 생성과 종료 컴퓨터에서 실제 프로세스 생성과정을 보자. BOOTING : OS가 RAM 에 resent; OS가 가장 먼저 첫 process를 만든다. (ex. Linux: init ); 그 init … 프로세스 생성과 종료 프로세스 생성 프로세스는 프로세스에 의해 만들어진다. 컴퓨터에서 실제 프로세스 생성과정을 보자. BOOTING : OS가 RAM 에 resident OS가 가장 먼저 첫 process를 만든다. (ex. Linux: in..기록의 다채로움
- Table of Contents:
See more articles in the same category here: https://chewathai27.com/to/blog.
REAKWON :: [리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드
프로세스(process)
프로세스는 간단히 말하자면 실행 중인 프로그램을 의미합니다. 아마 여러분들은 컴퓨터를 하면서 아주 빈번하게 듣는 용어이기도 합니다.
실행 중인 프로그램이라??
컴퓨터에서 말하는 실행 중인 프로그램이라는 건 저장공간에 있는 실행 파일이 메모리를 할당받아 명령어를 수행한다는 것을 의미합니다.
우리가 예를 들어 한글이라는 프로그램을 설치했다고 가정합시다. 아직은 더블클릭으로 프로그램을 실행하지 않았으므로 프로세스가 아닙니다. 설치 후 보고서를 작성하기 위해 프로그램을 실행한다면 그 순한 한글이라는 프로세스가 생성되는 것입니다.
프로세스에서 프로세스를 생성할 수도 있습니다. 이 경우 생성한 프로세스는 부모 프로세스, 생성당한 프로세스를 자식프로세스라고 합니다.
코드 영역(혹은 텍스트 영역이라고도 함) : 프로세스의 명령어가 존재합니다. CPU는 이 영역에서 명령을 실행합니다. 위 그림에서는 부모 자식 프로세스간 별개로 텍스트 영역이 존재하는 것처럼 보이지만 실제로 코드영역은 프로 자식 프로세스간 공유가 됩니다.
데이터 영역 : 전역 변수와 정적 변수가 존재합니다. 프로그램이 실행하는 동시에 할당되고 종료시 소멸됩니다.
힙 영역 : 동적할당에 필요한 메모리입니다. 프로그램 종료시 해제되지 않고 직접 해제해 주어야 합니다. C언어에서는 free를 사용해서 해제하게 됩니다.
스택 영역 : 함수 호출 시 매개변수, 지역변수가 이 곳에 자리잡게 됩니다. 함수가 종료하게 되면 해제됩니다.
자식 프로세스를 생성하는 시스템 콜이 fork라고 합니다. fork는 자기 자신을 복사합니다만 결국 다른 별도의 메모리를 갖게 됩니다. 그래서 자식 프로세스에서 변수의 값을 바꾸어도 부모 프로세스에 영향을 주지 않게 되죠.
자식 프로세스 생성(fork)
프로세스를 하나 생성하면서 특징을 같이 이야기 해봅시다.
#include #include #include #include int g_var = 1; char buf[] = “a write to stdout
“; int main(){ int l_var; pid_t pid; l_var = 10; if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1){ printf(“write error
“); exit(0); } printf(“fork start!
“); //fflush(stdout); if((pid=fork())<0){ printf("fork error "); exit(0); }else if(pid == 0){ //child g_var++; l_var++; }else{ //parent sleep(3); } printf("pid = %ld, g_var = %d, l_var = %d ",(long)getpid(),g_var,l_var); exit(0); } 이제 컴파일 후 실행시켜보도록 하지요. # gcc fork.c # ./a.out a write to stdout fork start! pid = 83929, g_var = 2, l_var = 11 pid = 83928, g_var = 1, l_var = 10 위 코드를 설명하자면 fork를 통해서 자식 프로세스를 생성하게 됩니다. fork가 정상적으로 호출이되면 자식 프로세스는 0이 반환되고 부모 프로세스에게는 자식의 프로세스 id가 반환됩니다. 자식 프로세스는 변수들의 값을 변경시키고 있습니다. 그 결과는 위와 같습니다. 위의 실행 결과를 파일로 저장해볼까요? # ./a.out > fork.out # cat fork.out a write to stdout fork start! pid = 83960, glob = 2, var = 11 fork start! pid = 83959, glob = 1, var = 10
자세히보면 fork_start!라는 문자열이 두번 출력되고 있네요. 이같은 결과가 우리에게 시사하는 바는 자식 프로세스는 부모 프로세스의 버퍼까지 복사한다는 사실입니다. printf는 내부적으로 write 시스템 콜을 사용합니다. printf는 write를 최소한으로 그리고 효율적으로 사용하기 위해 내부적으로 버퍼를 사용하고 있습니다. 터미널 콘솔에 출력할때는 l줄 단위 버퍼링 방식을 사용합니다. 이 말은 줄이 바뀌면(엔터) 화면에 출력한다는 것입니다. 이와 다르게 파일에 기록할때는 full 버퍼링 방식을 사용합니다. 이 방식은 버퍼가 전부 채워져야 파일에 기록하는 버퍼링 방식입니다.
위의 테스트 결과에서 내용을 파일(fork.out)에 저장하는 것이므로 full buffering 방식을 사용하며 이때 버퍼가 다 차지 않았고 이 후에 자식 프로세스를 생성했습니다. 자식 프로세스는 부포 프로세스의 버퍼까지 전부 복제하였고 프로그램이 종료가 될때 버퍼를 비우며 종료가 됩니다.
위의 fflush(stdout)을 주석해제하고 다시 실행해보시기 바랍니다. fork전에 버퍼를 다 방출하기 때문에 fork_start!라는 문자열이 한번만 출력됩니다.
고아 프로세스
프로세스를 생성하기만 하면 다가 아닙니다. 왜 그런지 실행과 결과를 보면서 이야기 하도록 하지요.
#include
#include #include int main(){ int pid; if((pid=fork())==0){ sleep(1); printf(“\t [child process %d] created from %d “, getpid(),getppid()); exit(0); } printf(“[parent process %d] create %d process, ppid:%d
“, getpid(),pid,getppid()); }
getpid : 현재 프로세스의 pid를 반환합니다.
getppid : 부모의 프로세스 id를 반환하지요.
이 코드의 결과를 예상해보세요.
자식 프로세스에서는 부모의 프로세스 ID를 출력하니까 부모의 getpid와 같을 겁니다. 그럴까요??
[parent process 21162] create 21163 process, ppid:20819 [child process 21163] created from 1????
우리의 예상과는 다른데요??
분명 부모프로세스의 pid는 21162인데 자식 프로세스의 부모프로세스 pid가 쌩뚱맞은 1입니다.
왜 그럴까요??
우선 이 코드는 문제가 좀 있는 코드입니다. 부모프로세스가 먼저 종료되었기 때문입니다. 자식이 먼저 종료된다는 사실을 확보하기 위해 1초를 기다리는 이유가 바로 이 사실을 확인하기 위해서 입니다. 자식 프로세스는 부모 프로세스를 잃어 다른 부모를 찾게 됩니다. 바로 init 프로세스라고 하는데요. init프로세스의 pid가 바로 1입니다.
그러니까 모든 프로세스의 선조가 init프로세스라는 이야기죠.
이런 프로세스를 부모를 잃었다고 하여 고아프로세스가 됩니다.
그렇기 때문에 모든 부모님이 그렇듯 자식을 기다려주어야합니다. 그렇기 때문에 wait이라는 시스템 콜이 있지요.
코드를 수정해서 다시 확인합시다.
#include
#include #include #include #include int main(){ int pid; int status; int terminatedPid; if((pid=fork())==0){ printf(“\t [child process %d] created from %d “, getpid(),getppid()); sleep(1); exit(0); } printf(“[parent process %d] create %d process, ppid:%d
“, getpid(),pid,getppid()); terminatedPid=wait(&status); printf(“[parent process ] process wait process %d, status %d
“, terminatedPid,status); }
wait : wait의 첫번째 인자는 상태를 나타냅니다. 종료 상태를 받아올 수 있는 거죠. 반환값은 종료된 자식의 pid를 반환합니다.
[parent process 21803] create 21804 process, ppid:20819 [child process 21804] created from 21803 [parent process ] process wait process 21804, status 0이제 우리가 원하는 결과가 나왔군요. 자식프로세스를 기다려야합니다.
좀비프로세스
하지만 반대로 부모프로세스는 종료하지 않았지만 자식 프로세스가 먼저 종료하게 되면 어떨까요? 단, wait 호출을 하지 않고 말입니다.
#include
#include #include int main(){ if(fork()==0){ printf(“\t child process created and exit “); exit(0); } printf(“parent process sleep
“); sleep(10); sleep(10); printf(“process exit
“); }
이 후 컴파일하고 실행합니다.
# gcc process.c # ./a.out & [1] 22111 parent process sleep child process created and exit process exit
# ps -ef | grep 22111 … root 22112 22111 0 10:26 pts/2 00:00:00 [a.out]
… 부모 프로세스는 총 20초간 수행합니다. pid가 22111인 부모프로세스의 자식 프로세스 22112가 defunct인것이 보이세요? 바로 좀비 프로세스입니다.
부모프로세스는 자식 프로세스를 기다리고 있지 않아 종료상태를 얻지 못하고 있습니다. 그게 좀비 프로세스가 된 이유인데요. 이 종료 상태는 의외로 중요한데 예를 들어 쉘스크립트를 통해 명령을 실행할때 종료상태에 따라 분기를 하기 위해서 사용되기도 합니다.
부모 프로세스가 종료상태를 얻어와야 커널에서 자식 프로세스의 정보를 갖고 있는 구조체를 해제할 수 있습니다. 그 구조체가 task_struct라는 구조체입니다.
부모 프로세스는 커널이 관리하는 task_struct 구조체에서 멤버 pid와 exit_code를 통해서 종료상태를 얻어올 수 있습니다. 비록 좀비프로세스는 직접적으로 CPU, 메모리를 소모하지는 않지만 task_struct를 유지해야하기 때문에 메모리 낭비가 있게 되지요.
task_struct에서 pid와 exit_code, 무엇이 생각나세요?
wait의 반환값과 인자값입니다. 그 때문에 wait을 통해 해결할 수 있습니다.
코드를 다음과 같이 수정해보세요.
#include
#include #include #include #include int main(){ if(fork()==0){ printf(“\tchild process created and exit “); exit(0); } printf(“parent process sleep
“); wait(NULL); sleep(10); sleep(10); printf(“process exit
“); }
부모 프로세스에서 바로 wait 콜을 호출합니다. 그러니까 종료상태를 알게 되죠. 결과도 그럴까요?
# gcc process.c # ./a.out & [1] 23839 parent process sleep child process created and exit
# ps -ef | grep 23839 root 23839 20819 0 11:31 pts/2 00:00:00 ./a.out root 23842 20819 0 11:31 pts/2 00:00:00 grep –color=auto 23839 process exit
오.. 좀비프로세스가 없어졌어요.
또는 시그널을 이용한 방법도 있습니다.
#include
#include #include #include #include #include void childSignal(int sig){ printf(“signal:%d “,sig); wait(NULL); } int main(){ signal(SIGCHLD,childSignal); if(fork()==0){ printf(“\tchild process created and exit
“); exit(0); } printf(“parent process sleep
“); sleep(10); sleep(10); printf(“process exit
“); }
(시그널을 알아야 코드를 이해하는 데 도움이 됩니다.)
그리고 컴파일하고 실행해보도록 합시다. 그리고 재빠르게 ps 명령어를 쳐서 확인해보세요.
# gcc process.c # ./a.out & [1] 23451 parent process sleep child process created and exit signal:17
# ps -ef | grep 23451 root 23451 20819 0 11:09 pts/2 00:00:00 ./a.out root 23461 20819 0 11:10 pts/2 00:00:00 grep –color=auto 23451 process exit
이제 좀비프로세스가 없어졌군요!
왜 그럴까요??
우선 부모프로세스는 시그널을 등록합니다. 핸들러는 childSignal입니다.
자식 프로세스가 종료가 되면 시그널 번호 SIGCHLD로 부모프로세스에 시그널을 보냅니다.
그렇다면 그 순간 시그널 핸들러가 동작하죠. 그렇다면 부모프로세스는 childSignal함수안에 wait을 호출합니다.
그러므로 종료상태를 받을 수 있게 되고 자식 프로세스는 좀비프로세스가 되지 않습니다.
아차, sleep함수는 일정 시간 동안 시그널을 기다립니다. 그 시간안에 시그널을 받는 다면 sleep은 동작을 멈추게 됩니다.
왜 2번 sleep을 호출하는지 알겠죠? 바로 ps를 입력할 시간을 확보하기 위해서 입니다.
자식 프로세스의 종료 상태 알아오기
이번에는 wait에 인자를 이용하여 자식 프로세스의 종지 상태를 알아보도록 합시다. 종지상태를 조사하는 매크로는 표에 나와있습니다.
매크로 설명 WIFEXITED(status) 자식프로세스가 정상적으로 종료되었으면 true를 반환합니다. 이때 자식이 exit, _exit, _Exit으로 넘겨준 인수의 하위 8비트를 얻을 수도 있습니다. 그럴때 WEXITSTATUS(status)를 사용하세요. WIFSIGNALED(status) 만일 자식프로세스가 신호를 받아 비정상적으로 종료되었다면 참을 반환합니다. 이때 신호번호를 알려면 WTERMSIG(status)를 이용하여 알 수 있습니다. WCOREDUMP(status) 매크로를 지원하는 시스템은 코어덤프를 생성했는지도 알 수 있습니다. WIFSTOPPED(status) 자식 프로세스가 정지된 상태라면 참을 반환합니다. 이때 정지를 유발한 신호는 WSTOPSIG(status)를 통해 알아낼 수 있습니다. WIFCONTINUED(status) 만일 자식이 작업 제어 정지 이후 재개되었으면 참을 반환합니다.
다음은 위의 매크로를 사용한 예제입니다.
#include
#include #include #include void pr_exit(int status){ if(WIFEXITED(status)) printf(“normal termination, exit status = %d “,WEXITSTATUS(status)); else if(WIFSIGNALED(status)) printf(“abnormal termination, signal number =%d%s
“,WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? ” (core file generated)”: “”); #else “”); #endif else if(WIFSTOPPED(status)) printf(“child stopped, signal number = %d
“, WSTOPSIG(status)); } int main(void){ pid_t pid; int status; if((pid=fork())<0){ printf("fork error "); exit(0); }else if(pid==0) exit(7); if(wait(&status) != pid){ printf("wait error "); exit(0); } pr_exit(status); if((pid=fork())<0){ printf("fork error "); exit(0); }else if(pid==0) //abort signal abort(); if(wait(&status) != pid){ printf("wait error "); exit(0); } pr_exit(status); if((pid=fork()) < 0){ printf("fork erorr "); exit(0); }else if(pid==0) // 0으로 나누기 신호 status /=0; if(wait(&status)!=pid){ printf("wait error "); exit(0); } pr_exit(status); exit(0); } 컴파일 후 실행해보면 자식 프로세스가 어떻게 종료가 되었는지 알 수 있습니다. # ./a.out normal termination, exit status = 7 abnormal termination, signal number =6 (core file generated) abnormal termination, signal number =8 (core file generated) 자식 프로세스가 exit(7)로 종료되었음을 부모 프로세스는 WEXITSTATUS매크로로 알 수 가 있네요. 이제 fork와 wait을 사용하는 방법을 아시겠나요? 하지만 wait에는 문제점이 존재하는데요. 그 문제점을 보완하는 함수 waitpid가 존재합니다. 아래의 포스팅을 참고하세요. https://reakwon.tistory.com/99
UNIX〕프로세스 생성과 실행: pid, fork, exec, pipe
반응형
1. 프로세스(Process)
(1) 프로세스란?
현재 실행 중인 프로그램
즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당 받아 실행중인 것
운영체제의 제어 아래 실행(Running), 대기(Waiting), 중단(Stopped), 좀비(Zombie) 중 하나의 상태에 있게 된다.
각각의 프로세스가 가지는 고유의 번호를 PID(Process Idendification Number) 라고 한다.
예를 들어 데비안 리눅스가 부팅될 때에는 최상위 프로세스인 systemd(PID: 1)이 생성되며 모든 프로세스들은 이 1번 프로세스의 자식 프로세스가 된다.
부모 프로세스의 PID 를 줄여서 PPID 라고 한다.
ps 명령어를 통해 현재 실행중인 프로세스 목록을 확인할 수 있다.
(2) 쓰레드(Thread)
프로세스 내에서 실제로 작업을 수행하는 주체
모든 프로세스에는 하나 이상의 쓰레드가 존재한다.
두 개 이상의 쓰레드를 가지는 프로세스를 멀티 쓰레드 프로세스라고 한다.
특정한 시점에 프로그램의 특정한 부분을 수행하는 각각이 모두 쓰레드이다.
따라서 여러 개의 쓰레드를 이용하여 프로그램의 여러 부분을 동시에 수행시킬 수 있다.
싱글 쓰레드 프로세스와 멀티 쓰레드 프로세스 비교
각각의 프로세스들은 각각의 독립된 영역을 가지는 반면, 쓰레드는 하나의 프로세스 내에서 여러개 존재하기 때문에 같은 프로세스에 있는 메모리 공간을 서로 공유한다. 위의 사진에서 세 개의 쓰레드는 하나의 주소 공간에 존재하며 같은 데이터와 파일을 서로 공유하면서 코드의 여러 부분을 동시에 수행한다.
이 멀티 쓰레드가 각각 독립적으로 수행되기 위해서 가지는 두 가지 정보가 바로 CPU의 프로그램 카운터와 스택이다.
프로그램 카운터(Program Counter): 프로그램의 어느 부분을 실행하고 있는지에 대한 정보를 저장
스택(Stack): 함수를 호출하는 순서(Function Call)에 대한 정보를 저장
2. 프로세스 생성과 실행
사용자가 명령행에서 직접 프로세스를 실행하여 생성하는 경우 이외에도 프로그램 내에서 다른 프로그램을 실행하여 생성하는 경우도 있다. 위와 같이 system(), fork(), 또는 vfork() 함수를 통해 프로세스를 실행하거나 생성할 수 있다.
프로그램 실행 int system(const char *string); 프로세스 생성 pid_t fork(void); 또는 pid_t vfork(void);
(1) system()
system() 함수를 이용하여 간단하게 프로그램 안에서 새로운 프로그램을 실행시킬 수 있다.
기존의 명령이나 실행 파일명을 인자로 받아 쉘에 전달한다.
쉘은 내부적으로 새로운 프로세스를 생성하여 인자로 받은 명령을 실행한다.
해당 명령이 끝날 때까지 기다렸다가 종료 상태를 리턴한다.
가장 간단하지만 명령을 실행하기 위해 쉘까지 동작시키기 때문에 비효율적인 방법이다.
system(“leaks a.out”);
→ main 문 안에 적으면 프로그램이 종료되기 전에 메모리 누수를 확인해준다.
(2) fork()
fork() 함수로 생성된 새로운 프로세스를 자식 프로세스(Child Process)라고 한다.
fork() 함수를 호출한 프로세스는 부모 프로세스(Parent Process)가 된다.
fork() 함수가 리턴하면 부모 프로세스와 자식 프로세스가 동시에 동작하게 된다.
어느 프로세스가 먼저 실행되는지는 알 수 없으며 유닉스 운영체제의 스케쥴링에 따라 처리 순서가 달라진다.
fork() 함수 처리 과정:
#1. fork() 함수 호출
#2. 새로운 프로세스(=자식 프로세스) 생성
#3. 부모 프로세스의 데이터 영역을 그대로 복사하여 자식 프로세스에 메모리를 할당해 준다.
#4. 부모 프로세스에는 자식 프로세스의 PID를, 자식 프로세스에는 0 을 반환한다.
(3) exec()
현재 프로그램의 텍스트, 데이터, 스택 영역에 exec() 계열의 함수의 인자로 전달된 프로그램의 텍스트, 데이터, 스택 영역을 덮어씌우는 함수이다. 즉, 새로운 프로세스가 현재 프로세스 위치에 덮어씌워지므로 현재 프로세스는 종료된다.
#include
#include int main() { printf(“exec()함수 호출전 “); execl(“/bin/ls” , “ls” , “-l” , (char *) 0); printf(“exec()함수 호출 후
“); return 0; }
위의 예시에서 출력되는 것은 “exec() 함수 호출전” 뿐이다. execl()이 ls 프로세스를 실행함과 동시에 현재 프로세스가 종료되었기 때문이다. 만약 현재 프로세스를 종료시키고 싶지 않다면 아래처럼 fork() 함수를 이용한 뒤에 exec() 을 사용하면 된다.
#include
#include int main() { int pid; if((pid=fork()) == 0) { printf(“Child Process ID:%d “, getpid()); execl(“/bin/ls”, “ls”, “-l”, (char *)0); } else { printf(“Parent Process ID: %d
“, getpid()); } return 0; }
자식 프로세스에서 exec 함수를 호출하면 부모 프로세스로부터 복사한 프로그램과는 다른 명령이나 프로그램을 실행 가능하다. 부모 프로세스와 자식 프로세스가 각각 다른 작업을 수행해야 하는 경우 fork()와 exec() 함수를 함께 사용해야한다.
예를 들어 쉘에서 ‘ls -a’ 라는 명령어를 입력 받으면 쉘은 fork() 함수로 자식 프로세스를 만들고 사용자가 입력한 명령 ‘ls -a’를 exec() 함수군을 사용하여 자식 프로세스에서 실행한다.
■ execve()
#include
int execve(const char *filename, char *const argv[], char *const envp[]);
filename: 실행 가능한 binary file, shell script, 또는 명령어
argv: main 에서의 argv 와 똑같지만, main 과 달리 argc 가 없으므로 마지막에는 NULL 이 있다.
envp: key=value 형식으로 구성되어 있는 환경 변수 문자열 배열리스트로 마지막에는 NULL 이 있다. 예를 들어 envp[0] 이 “HOME=/home/user” 라면 key 는 “HOME”, value 는 “/home/user” 가 된다. 만약 이미 설정된 환경 변수를 사용하려면 environ 환경 변수를 사용할 수 있다. environ 은 C 프로그램에서 이미 선언되어 있기 때문에 extern 문을 통해 environ 변수를 참조하여 환경 변수 목록을 확인할 수 있다.
#include
extern char **environ; int main() { int i = 0; printf(“=== environment list === “); for(i=0; environ[i];i++) { printf(“<%2d>: %s
“, i, environ[i]); } return 0; }
ls 명령어 실행하는 프로그램 만들기
#include
#include #include #include #include extern char **environ; int main(int argc, char *argv[]) { char **new_argv; char command[] = “ls”; int idx; new_argv = (char **)malloc(sizeof(char *) * (argc + 1)); new_argv[0] = command; // 명령어를 ls로 변경 // command line으로 넘어온 parameter를 그대로 사용 for (idx = 1; idx < argc; idx++) new_argv[idx] = argv[idx]; // argc를 execve 파라미터에 전달할 수 없기 때문에 NULL이 파라미터의 끝을 의미한다. new_argv[argc] = NULL; if (execve("/bin/ls", new_argv, environ) == -1) { fprintf(stderr, "프로그램 실행 error: %s ", strerror(errno)); return 1; } // ls 명령어 binary로 실행로직이 교체되었으므로 이후의 로직은 절대 실행되지 않는다. printf("이것은 이제 ls 명령어러 이 라인은 출력되지 않는다. "); return 0; } (4) wait() fork() 함수를 통해 자식 프로세스를 생성하고 나면 부모 프로세스와 자식 프로세스 중에 어느 것이 먼저 실행되는지 알 수 없으며 먼저 실행을 마친 프로세스는 종료한다. 하지만 이들 사이의 종료 절차가 제대로 진행되지 않는 경우가 있다. 이 때 좀비 프로세스(Zombie Process)라는 불안정 상태의 프로세스가 발생한다. 이를 방지하기 위해 필요한 것이 프로세스 동기화이다. 부모 프로세스와 자식 프로세스를 동기화하려면 부모 프로세스는 자식 프로세스가 종료할 때까지 기다려야한다. 여기서 자식 프로세스의 실행이 완전히 끝나기를 기다렸다가 종료 상태를 확인하는 함수가 wait() 함수이다. 자식 프로세스의 종료 상태는 stat_loc 에 지정한 주소에 저장된다. 만약 부모 프로세스가 wait() 함수를 호출하기 전에 자식 프로세스가 종료하면 wait() 함수는 즉시 리턴한다. 리턴값은 자식 프로세스의 ID 이다. 만약 리턴값이 -1 이라면 살아있는 자식 프로세스가 하나도 없다는 것을 의미한다. ※ wait() 함수의 경우 아무 자식 프로세스나 종료하면 리턴하지만 waitpid() 함수는 특정 PID 의 자식 프로세스가 종료하기를 기다린다는 점에서 다르다. #include #include
pid_t wait(int *stat_loc);
※ stat_loc: 상태 정보를 저장하는 주소
pid_t waitpid(pid_t pid, int *status, int options);
※ status: 프로세스의 상태정보를 저장하는 주소
waitpid() 함수에서 status 는 프로세스의 상태를 나타내기 위해 사용된다. status 가 NULL 이 아닌 경우, status 가 가리키는 위치에 프로세스의 상태정보를 저장한다. 여기서 상태정보를 알아내기 위해 사용되는 매크로들 중에서 가장 대표적인 것이 WIFEXITED(status) 이다. 자식 프로세스가 정상적으로 종료되었다면 TRUE(non-zero) 이다.
3. pipe()
(1) 형식
#include
int pipe(int fd[2]);
(2) 작동 원리
출처: https://reakwon.tistory.com/80
pipe 함수가 성공적으로 호출되는 경우 0, 실패했을 경우에는 -1 을 반환
파이프는 사진과 같이 커널 영역에 생성되며 프로세스는 파일 디스크립터만을 가지고 있다.
파일 디스크립터 fd[0] 은 읽기용 파이프이고 fd[1] 은 쓰기용 파이프이다.
현재 프로세스에서 fork() 하게되면 자식 프로세스가 생성된다.
부모 프로세스의 파일 디스크립터는 그대로 자식 프로세스에 복제된다.
#include
#include #include #include #define MAX_BUF 1024 #define READ 0 #define WRITE 1 int main(){ int fd[2]; pid_t pid; char buf[MAX_BUF]; if(pipe(fd) < 0) { printf("pipe error "); exit(1); } if((pid=fork())<0) { printf("fork error "); exit(1); } printf(" "); if(pid>0) //parent process { close(fd[READ]); strcpy(buf,”message from parent “); write(fd[WRITE],buf,strlen(buf)); } else //child process { close(fd[WRITE]); read(fd[READ],buf,MAX_BUF); printf(“child got message : %s
“,buf); } exit(0); }
우선 부모 프로세스에서 파이프를 생성하여 파이프에 데이터를 쓰기(fd[1]: stdout)만 할것이므로 읽기 파이프(fd[0]: stdin)는 닫고, fd[1] 에 데이터를 쓴다.
자식 프로세스에서 쓰기 파이프는 쓰지 않으므로 fd[1] 은 닫고 읽기 파이프를 통해 데이터를 읽는다.
출력 결과는 child got message : message from parent 이 된다.
반응형
[Linux Kernel] 커널 스레드(프로세스) 생성 과정 소스 분석
다크엔지니어
systemd 프로세스는 유저 공간에서 생성된 프로세스의 부모 프로세스 역할을 수행한다.
보통은 init 프로세스라 부르기도 한다.
보통 유저 프로세스에서 부모 프로세스가 소멸 되면, init 프로세스가 부모 역할을 수행하게 된다.
ps -ejH
프로세스 생성 과정
크게 두가지로 분류 할 수 있다.
1. 유저 레벨에서 생성된 프로세스
라이브러리(GNU C : glibc) 의 도움을 받아 커널에게 프로세스 생성 요청
2. 커널 레벨에서 생성된 프로세스
커널 내부의 kthread_create() 함수를 호출하여 커널 프로세스 생성
대부분 이를 커널 스레드 라고 부른다.
둘의 공통점은 _do_fork() 함수를 호출한다는 점이다.
init 프로세스 : 유저 레벨 프로세스 생성
kthreadd 프로세스 : 커널 레벨 프로세스 생성
두 프로세스는 생성 역할을 맡게 되는데, 두 프로세스의 리소스를 copy 하여 만들기 때문이다.
이유는 속도 향상을 위해서 이다.
실제로 리눅스 커널에서는 위와같은 방식으로 속도 향상에 신경을 많이 쓴다.
바로 커널 메모리 할당자인 Slub Memory Allocator 도 이와 유사한 기법을 활용한다.
드라이버에서 메모리 할당을 요청할 때 자주쓰는 구조체를 정의해서
해당 구조체에 대한 메모리를 미리 확보해 놓는다.
아래 경로에 보면 유저영역에서 커널에 요청을 위한 라이브러리가 모여있다.
유저 코드는 이 라이브러리와 링킹되어 메모리에 적재돼 실행된다.
유저 레벨 프로세스 생성
위 그림은 이전 글에서 ftrace 로 분석한 유저 레벨에서의 프로세스 생성 과정 이다.
커널 레벨 프로세스 생성
위 그림을 간단히 설명하면, 크게 두 가지로
1. kthreadd 프로세스에게 커널 프로세스 생성 요청
kthread_create() kthread_create_on_node()
2. kthreadd 프로세스는 깨어나 자신에게 커널 프로세스 생성 요청 점검 후 생성
kthreadd() create_kthread()
리눅스 드라이버에서 워크를 워크큐에 큐잉하면 커널은 커널 스레드의 종류인 워커 스레드를 더 생성한다.
커널에서 메모리가 부족하면 페이지를 확보하는 kswapd 스레드를 깨워 실행한다.
즉, 리눅스 커널 시스템이 많은 일을 할때, 커널 스레드를 생성한다.
커널 스레드는 커널 공간에서 리소스 관리로 메모리와 전원 부분을 관리한다.
커널 스레드는 커널 공간에서만 생성되며, 유저 공간과 상호작용 하지 않는다.
커널에서 직접 실행, 휴먼 동작을 직접 제어하고, 대부분 시스템 부팅 시 생성 되고, 종료 시 까지
백그라운드로 실행 된다.
커널 스레드 항목 확인
kthreadd 프로세스로 커널 스레드들의 부모 프로세스 이다.
스레드 핸들러 함수는 kthreadd() 이다.
워커 스레드는 워크큐에 큐잉된 워크를 실행하는 프로세스 이다.
스레드 핸들러 함수는 worker_thread() 이다. process_one_work() 를 호출해 워크를 실행하는 기능 수행
ksoftirqd 프로세스는 soft irq 를 위해 실행하는 프로세스 이다.
smp_boot 형태의 스레드 이며, run_ksoftirqd() 함수가 스레드 핸들러 이며, Soft irq 서비스를 실행
_do_softirq()함수에서 ksoftirqd를 깨운다.
irq/86-mmc1 스레드는 irq 스레드 라고 하며, 인터럽트 후반부 처리를 위해 사용된다.
이름으로 기능을 유추 할 수 있다. 86번 mmc1 인터럽드의 후반부 처리하는 irq 스레드 인 것 이다.
ps axjf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 2 0 0 ? -1 S 0 0:00 [kthreadd] 2 3 0 0 ? -1 I< 0 0:00 \_ [rcu_gp] 2 4 0 0 ? -1 I< 0 0:00 \_ [rcu_par_gp] 2 8 0 0 ? -1 I< 0 0:00 \_ [mm_percpu_wq] 2 9 0 0 ? -1 S 0 0:00 \_ [rcu_tasks_rude_] 2 10 0 0 ? -1 S 0 0:00 \_ [rcu_tasks_trace] 2 11 0 0 ? -1 S 0 0:00 \_ [ksoftirqd/0] 2 12 0 0 ? -1 I 0 0:02 \_ [rcu_sched] 2 13 0 0 ? -1 S 0 0:00 \_ [migration/0] 2 14 0 0 ? -1 S 0 0:00 \_ [cpuhp/0] 2 15 0 0 ? -1 S 0 0:00 \_ [cpuhp/1] 2 16 0 0 ? -1 S 0 0:00 \_ [migration/1] 2 17 0 0 ? -1 S 0 0:00 \_ [ksoftirqd/1] 2 20 0 0 ? -1 S 0 0:00 \_ [cpuhp/2] 2 21 0 0 ? -1 S 0 0:00 \_ [migration/2] 2 22 0 0 ? -1 S 0 0:00 \_ [ksoftirqd/2] 2 25 0 0 ? -1 S 0 0:00 \_ [cpuhp/3] 2 26 0 0 ? -1 S 0 0:00 \_ [migration/3] 2 27 0 0 ? -1 S 0 0:00 \_ [ksoftirqd/3] 2 30 0 0 ? -1 S 0 0:00 \_ [kdevtmpfs] 2 31 0 0 ? -1 I< 0 0:00 \_ [netns] 2 34 0 0 ? -1 S 0 0:00 \_ [kauditd] 2 36 0 0 ? -1 S 0 0:00 \_ [khungtaskd] 2 37 0 0 ? -1 S 0 0:00 \_ [oom_reaper] 2 38 0 0 ? -1 I< 0 0:00 \_ [writeback] 2 39 0 0 ? -1 S 0 0:00 \_ [kcompactd0] 2 59 0 0 ? -1 I< 0 0:00 \_ [kblockd] 2 60 0 0 ? -1 I< 0 0:00 \_ [blkcg_punt_bio] 2 61 0 0 ? -1 S 0 0:00 \_ [watchdogd] 2 62 0 0 ? -1 I< 0 0:00 \_ [kworker/0:1H-kblockd] 2 63 0 0 ? -1 I< 0 0:00 \_ [rpciod] 2 64 0 0 ? -1 I< 0 0:00 \_ [kworker/u9:0-hci0] 2 65 0 0 ? -1 I< 0 0:00 \_ [xprtiod] 2 66 0 0 ? -1 S 0 0:00 \_ [kswapd0] 2 69 0 0 ? -1 I< 0 0:00 \_ [nfsiod] 2 70 0 0 ? -1 I< 0 0:00 \_ [kthrotld] 2 71 0 0 ? -1 I< 0 0:00 \_ [iscsi_eh] 2 72 0 0 ? -1 I< 0 0:00 \_ [iscsi_destroy] 2 73 0 0 ? -1 I< 0 0:00 \_ [nvme-wq] 2 74 0 0 ? -1 I< 0 0:00 \_ [nvme-reset-wq] 2 75 0 0 ? -1 I< 0 0:00 \_ [nvme-delete-wq] 2 78 0 0 ? -1 I< 0 0:00 \_ [DWC Notificatio] 2 79 0 0 ? -1 I< 0 0:00 \_ [uas] 2 80 0 0 ? -1 S< 0 0:00 \_ [vchiq-slot/0] 2 81 0 0 ? -1 S< 0 0:00 \_ [vchiq-recy/0] 2 82 0 0 ? -1 S< 0 0:00 \_ [vchiq-sync/0] 2 83 0 0 ? -1 I< 0 0:00 \_ [zswap-shrink] 2 87 0 0 ? -1 I< 0 0:00 \_ [sdhci] 2 88 0 0 ? -1 S 0 0:00 \_ [irq/66-mmc0] 2 90 0 0 ? -1 I< 0 0:00 \_ [mmc_complete] 2 91 0 0 ? -1 I< 0 0:00 \_ [kworker/3:1H-kblockd] 2 93 0 0 ? -1 S 0 0:00 \_ [jbd2/mmcblk0p2-] 2 94 0 0 ? -1 I< 0 0:00 \_ [ext4-rsv-conver] 2 96 0 0 ? -1 I< 0 0:00 \_ [ipv6_addrconf] 2 98 0 0 ? -1 I< 0 0:00 \_ [kworker/2:1H-kblockd] 2 102 0 0 ? -1 I< 0 0:00 \_ [kworker/1:1H-kblockd] 2 192 0 0 ? -1 S 0 0:00 \_ [vchiq-keep/0] 2 195 0 0 ? -1 S< 0 0:00 \_ [SMIO] 2 208 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 210 0 0 ? -1 S 0 0:04 \_ [v3d_bin] 2 212 0 0 ? -1 S 0 0:05 \_ [v3d_render] 2 215 0 0 ? -1 S 0 0:00 \_ [v3d_tfu] 2 216 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 217 0 0 ? -1 S 0 0:00 \_ [v3d_csd] 2 218 0 0 ? -1 S 0 0:00 \_ [v3d_cache_clean] 2 223 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 225 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 227 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 231 0 0 ? -1 I< 0 0:00 \_ [mmal-vchiq] 2 260 0 0 ? -1 I< 0 0:00 \_ [cfg80211] 2 271 0 0 ? -1 I< 0 0:00 \_ [brcmf_wq/mmc1:0] 2 273 0 0 ? -1 S 0 0:00 \_ [brcmf_wdog/mmc1] 2 470 0 0 ? -1 S 0 0:00 \_ [irq/57-vc4 hdmi] 2 471 0 0 ? -1 S 0 0:00 \_ [irq/58-vc4 hdmi] 2 473 0 0 ? -1 S 0 0:00 \_ [cec-vc4] 2 480 0 0 ? -1 S 0 0:00 \_ [irq/54-vc4 hdmi] 2 481 0 0 ? -1 S 0 0:00 \_ [irq/53-vc4 hdmi] 2 499 0 0 ? -1 S 0 0:00 \_ [irq/63-vc4 hdmi] 2 501 0 0 ? -1 S 0 0:00 \_ [irq/64-vc4 hdmi] 2 502 0 0 ? -1 S 0 0:00 \_ [cec-vc4] 2 503 0 0 ? -1 S 0 0:00 \_ [irq/60-vc4 hdmi] 2 508 0 0 ? -1 S 0 0:00 \_ [irq/59-vc4 hdmi] 2 511 0 0 ? -1 S 0 0:00 \_ [card1-crtc0] 2 513 0 0 ? -1 S 0 0:00 \_ [card1-crtc1] 2 514 0 0 ? -1 S 0 0:00 \_ [card1-crtc2] 2 515 0 0 ? -1 S 0 0:00 \_ [card1-crtc3] 2 516 0 0 ? -1 S 0 0:00 \_ [card1-crtc4] 2 517 0 0 ? -1 S 0 0:00 \_ [card1-crtc5] 2 712 0 0 ? -1 I< 0 0:00 \_ [kworker/u9:2-hci0] 2 774 0 0 ? -1 I< 0 0:00 \_ [cryptd] 2 817 0 0 ? -1 S< 0 0:00 \_ [krfcommd] 2 1029 0 0 ? -1 I 0 0:00 \_ [kworker/2:0-mm_percpu_wq] 2 1157 0 0 ? -1 I 0 0:00 \_ [kworker/1:1-events] 2 1385 0 0 ? -1 I< 0 0:00 \_ [kworker/1:2H] 2 1425 0 0 ? -1 I< 0 0:00 \_ [kworker/2:0H] 2 1593 0 0 ? -1 I 0 0:00 \_ [kworker/u8:1-events_unbound] 2 1596 0 0 ? -1 I 0 0:00 \_ [kworker/1:0] 2 1650 0 0 ? -1 I< 0 0:00 \_ [kworker/0:0H] 2 1651 0 0 ? -1 I 0 0:00 \_ [kworker/3:2-events] 2 1683 0 0 ? -1 I 0 0:00 \_ [kworker/u8:3-events_unbound] 2 1684 0 0 ? -1 I 0 0:00 \_ [kworker/0:1-events] 2 1685 0 0 ? -1 I 0 0:00 \_ [kworker/3:1-events] 2 1704 0 0 ? -1 I 0 0:00 \_ [kworker/2:1] 2 1705 0 0 ? -1 I< 0 0:00 \_ [kworker/3:2H] 2 1718 0 0 ? -1 I 0 0:00 \_ [kworker/3:0-events] 2 1720 0 0 ? -1 I 0 0:00 \_ [kworker/0:0-events] 2 1721 0 0 ? -1 I 0 0:00 \_ [kworker/3:3-events] 2 1734 0 0 ? -1 I 0 0:00 \_ [kworker/0:2-events] 2 1735 0 0 ? -1 I 0 0:00 \_ [kworker/u8:0-brcmf_wq/mmc1:0001:1] 2 1740 0 0 ? -1 I 0 0:00 \_ [kworker/u8:2-events_unbound] 0 1 1 1 ? -1 Ss 0 0:03 /sbin/init splash 1 130 130 130 ? -1 Ss 0 0:01 /lib/systemd/systemd-journald 1 161 161 161 ? -1 Ss 0 0:00 /lib/systemd/systemd-udevd 1 353 353 353 ? -1 Ss 108 0:00 avahi-daemon: running [raspberrypi.local] 353 359 353 353 ? -1 S 108 0:00 \_ avahi-daemon: chroot helper 1 354 354 354 ? -1 Ss 0 0:00 /usr/sbin/cron -f 1 355 355 355 ? -1 Ss 104 0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only 1 374 374 374 ? -1 Ssl 0 0:00 /usr/libexec/polkitd --no-debug 1 401 401 401 ? -1 Ssl 0 0:00 /usr/sbin/rsyslogd -n -iNONE 1 418 418 418 ? -1 Ss 0 0:00 /usr/sbin/dhcpcd -b 1 430 430 430 ? -1 Ss 0 0:00 /lib/systemd/systemd-logind 1 431 431 431 ? -1 Ss 65534 0:00 /usr/sbin/thd --triggers /etc/triggerhappy/triggers.d/ --socket /run/thd.socket --user nobody --deviceglob /dev/input/event* 1 434 434 434 ? -1 Ssl 0 0:00 /usr/libexec/udisks2/udisksd 1 436 436 436 ? -1 Ss 0 0:00 /sbin/wpa_supplicant -u -s -O /run/wpa_supplicant 1 486 486 486 ? -1 Ssl 0 0:00 /usr/sbin/ModemManager 1 496 496 496 ? -1 Ss 115 0:00 /usr/bin/epmd -systemd 1 504 504 504 ? -1 SLsl 0 0:00 /usr/sbin/rngd -r /dev/hwrng 1 528 528 528 ? -1 Ssl 0 0:00 /usr/sbin/lightdm 528 586 586 586 tty7 586 Ssl+ 0 0:45 \_ /usr/lib/xorg/Xorg :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch 528 646 528 528 ? -1 Sl 0 0:00 \_ lightdm --session-child 18 21 646 672 672 672 ? -1 Ssl 109 0:17 | \_ /usr/sbin/pi-greeter 528 697 528 528 ? -1 S 0 0:00 \_ lightdm --session-child 14 21 1 587 587 587 ? -1 Ss 0 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 587 918 918 918 ? -1 Ss 0 0:00 \_ sshd: devk [priv] 918 953 918 918 ? -1 S 1001 0:03 | \_ sshd: devk@pts/0 953 956 956 956 pts/0 1743 Ss 1001 0:00 | \_ -bash 956 977 977 956 pts/0 1743 S 0 0:00 | \_ sudo su 977 978 977 956 pts/0 1743 S 0 0:00 | \_ su 978 979 979 956 pts/0 1743 S 0 0:00 | \_ bash 979 1743 1743 956 pts/0 1743 R+ 0 0:00 | \_ ps axjf 587 920 920 920 ? -1 Ss 0 0:00 \_ sshd: devk [priv] 920 975 920 920 ? -1 S 1001 0:00 | \_ sshd: devk@notty 975 976 976 976 ? -1 Ss 1001 0:00 | \_ /usr/lib/openssh/sftp-server 587 1555 1555 1555 ? -1 Ss 0 0:00 \_ sshd: devk [priv] 1555 1562 1555 1555 ? -1 S 1001 0:00 | \_ sshd: devk@pts/1 1562 1564 1564 1564 pts/1 1564 Ss+ 1001 0:00 | \_ -bash 587 1557 1557 1557 ? -1 Ss 0 0:00 \_ sshd: devk [priv] 1557 1581 1557 1557 ? -1 S 1001 0:00 \_ sshd: devk@notty 1581 1582 1582 1582 ? -1 Ss 1001 0:00 \_ /usr/lib/openssh/sftp-server 1 603 603 603 ? -1 Ss 0 0:00 /sbin/wpa_supplicant -s -B -P /run/wpa_supplicant.wlan0.pid -i wlan0 -D nl80211,wext -c /etc/wpa_supplicant/wpa_supplicant.conf 1 650 650 650 ? -1 Ss 109 0:00 /lib/systemd/systemd --user 650 651 650 650 ? -1 S 109 0:00 \_ (sd-pam) 650 670 670 670 ? -1 S
list 를 추가한다는 점이다. kthread 프로세스는 kthread_create_list 연결 리스트를 확인해 커널 스레드 생성 요청이 있었는지 확인 한다.
그리고 kthread 프로세스의 테스크 디스크립터인 kthread_task를 인자로 kthread 프로세스를 깨운다.
kthread 프로세스가 커널 스레드를 생성
kthreadd() 함수 분석
이렇게 위의 과정으로 kthread 프로세스를 깨우게 되면, 스레드 핸들러 kthreadd()함수가 실행이 된다.
kthreadd() 함술의 핵심 기능은 다음과 같다.
kthread_create_lnfo 연결 리스트를 확인해 프로세스 요청 확인
create_kthread() 함수를 호출해 프로세스 생성
list에 요청 건이 없다면, schdule()함수로 진입해 휴면상태에 진입 하고
요청이 있다면, __set_current_state 를 실행한다.
연결리스트 알고리즘에서 _list 는 head, 로서 info 정보를 담고있는 구조체에 next 로 접근한다.
_list의 next 로 마지막 _info 의 list_head 를 가리키게 된다.
바로 info 구조체에서 list 필드의 offset 을 계산해 info 구조체의 시작 주소를 알 수 있다.
그리고 create_kthread(create) 함수를 호출해 커널 스레드를 생성한다.
create_kthread() 함수 분석
kernel_thread 호출 시 CLONE_FS CLONE_FILES SIGCHLD flag OR 연산으로 설정한다.
즉, 리소스에 3개 해당 항목을 추가하게 된다.
kernel_thread() 함수 분석
kernel/fork.c
kernel_clone 함수 호출이 되며, 이는 유저 레벨 생성 시 에도 동일하게 호출되는 함수이다.
여기까지를 정리하자면,
kthread_create 를 통해 스레드 핸들러함수와 핸들러에 전달할 매개변수 그리고 이름을 지정하고,
kthraedd 프로세스가 깨어나면, list 와 info 연결리스트를 통해 요청 된 프로세스를 생성하게 된다.
kernel_clone, do_fork() 함수 분석
Armv8 부턴 do_fork 에서 kernel_clone 으로 변경 되었다.
kernel_clone 함수의 동작은 크게 두 가지가 있다.
copy_process 함수를 호출해서 프로세스를 생성, 부모 프로세스의 리소스를 자식 프로세스에게 복제
copy_process 함수 호출해 프로세스를 만든 후 wake up new task 함수로 프로세스를 꺠우고, 깨운다는 것은 스케줄러에게 프로세스 실행을 요청하는 것이다.
copy_process에서 리턴된 p 값으로 테스크 디스크립터의 주소를 담고 있는 변수로 오류가 있는지 검사한다.
그리고 ftrace 이벤트 중 sched_process_fork 를 활성화 해주는 함수가 trace_shced_process_fork 함수 이다.
wake_up_new_task 함수를 통해 생성한 프로세스를 깨운다. 그리고 생성한 PID 반환
copy_process() 함수 분석
사실 프로세스를 생성하는 핵심 동작이 이 함수에 구현되어 있다.
양이 많아서 핵심 부분만 설명 하자면,
아래 함수로 생성할 프로세스의 테스크 디스크립터인 task_struct 구조체와 프로세스가 실행 될 스택공간을 할당한다.
아래 함수를 통해 task_struct 스케줄링 정보를 초기화 한다.
부모 프로세스의 리소스들을 복사 한다.
wake_up_new_task() 함수 분석
프로세스 생성의 마지막 단계로 kernel/sched/core.c
다행히 길지 않다.. ㅎ
p->state 에 TASK_RUNNING 으로 상태 전환 후
__set_task_cpu 로 thread_info 구조체의 cpu 필드에 현재 실행 중인 cpu 번호를 할당한다.
나중에 모든 프로세스의 cpu 번호 출력은 thread_info 구조체를 통해 리스트 업 되어진다.
그리고 __task_rq_lock 을 시작으로 런큐 주소를 읽은 다음, activate_task 함수를 호출해
런큐에 새롭게 추가된 프로세스를 삽입한다.
이로서 커널 레벨에서의 커널 스레드 생성 과정을 소스로 알아보았다.
유저 레벨에서도 Armv8 기준인 kernel_clone 은 공통적으로 호출이 되어지는 것으로 보아
두 과정 모두 부모 프로세스가 다를뿐 부모의 리소스를 그대로 복제하여 자식 프로세스가 생성 됨을
알 수 있다.
아래는 참고를 위해 ftrace 에서 본 생성 종료 콜 스텍이다.
ftrace 를 통한 프로세스 생성 및 종료 함수 흐름(콜 스텍) process 1424
1. 프로세스 생성 단계의 함수 흐름
2. 프로세스 종료 단계의 함수 흐름
So you have finished reading the 리눅스 프로세스 생성 과정 topic article, if you find this article useful, please share it. Thank you very much. See more: Fork 함수 개념, 리눅스 좀비 프로세스 만들기