Linux 예제를 사용한 실행. UNIX 프로세스를 만드는 방법을 알아보세요. 목록1. ps 명령의 출력

EXEC(2)

이름
exec: execl, execv, execle, execve, execlp, execvp 파일 실행

통사론

Int execl (경로, arg0, arg1, ..., argn, (char*) 0) char *path, *arg0, *arg1, ..., *argn; int execv (경로, argv) char *path, *argv ; int execle (경로, arg0, arg1, ..., argn, (char*) 0, envp) char *path, *arg0, *arg1, ..., *argn, *envp ; int execve (경로, argv, envp) char *path, *argv , *envp ; int execlp (파일, arg0, arg1, ..., argn, (char*) 0) char *file, *arg0, *arg1, ..., *argn; int execvp (파일, argv) char *file, *argv ;

설명
모든 형태의 exec 시스템 호출은 호출 프로세스를 새로운 프로세스, 이는 일반 실행 파일에서 빌드되며 이후에는 새 실행 파일이라고 합니다. 실행 파일은 헤더로 구성됩니다. a.out(4)], 명령 세그먼트(.text) 및 데이터. 데이터는 초기화된 부분(.data)과 초기화되지 않은 부분(.bss)으로 구성됩니다. exec 시스템 호출이 성공하면 호출 프로세스가 이미 새 프로세스로 대체되었기 때문에 반환할 수 없습니다.

C 프로그램을 실행할 때 다음과 같이 호출합니다.

메인(argc, argv, envp) int argc; char **argv, **envp;

여기서 argc는 인수 수와 같고, argv는 인수 자체에 대한 포인터 배열이고, envp는 환경을 구성하는 문자열에 대한 포인터 배열입니다. 관례적으로 argc는 1 이상이고 argv 배열의 첫 번째 요소는 새 실행 파일의 이름이 포함된 문자열을 가리킵니다.

exec 그룹 시스템 호출에 대한 인수의 의미는 다음과 같습니다.

path 인수는 새 실행 파일의 경로 이름을 지정합니다.

path와 마찬가지로 file 인수는 새 실행 파일을 지정하지만 해당 파일의 경로는 전달된 디렉터리를 확인하여 결정됩니다. 환경 변수경로 [참조 환경(5) ]. 환경은 쉘에 의해 유지됩니다[ sh(1) 참조].

인수 arg0, arg1, ..., argn은 널 바이트로 구분된 문자열에 대한 포인터입니다. 이러한 체인은 새 프로세스에 사용할 수 있는 인수 목록을 형성합니다. 관례적으로 최소한 arg0이 존재해야 하며 path(또는 path)와 동일한 문자열을 가리켜야 합니다. 마지막 구성요소길).

argv 배열에는 널 바이트로 구분된 문자열에 대한 포인터가 포함되어 있습니다. 이러한 체인은 새 프로세스에 사용할 수 있는 인수 목록을 형성합니다. 관례적으로 argv는 최소한 path와 동일한 문자열(또는 path의 마지막 구성 요소)을 가리키는 첫 번째 요소를 포함해야 합니다. argv 배열의 마지막으로 점유된 요소 뒤에는 널 포인터가 와야 합니다.

envp 배열에는 널 바이트로 구분된 문자열에 대한 포인터가 포함되어 있습니다. 이러한 체인은 새로운 프로세스의 환경을 형성합니다. envp 배열의 마지막으로 점유된 요소 뒤에는 널 포인터가 와야 합니다.

프로그램 실행을 시작하기 전에 외부 변수 Environ에 대한 설명은 다음과 같습니다.

외부 char **환경;

프로세스 환경을 형성하는 기호 문자열에 대한 포인터 배열의 주소가 배치됩니다. 이 변수를 사용하면(main에 envp 인수를 사용하는 것도 포함) 사용된 exec 시스템 호출 버전에 관계없이 항상 새 프로세스에서 환경에 액세스할 수 있습니다. 유일한 차이점은 execle 및 execve 호출의 경우 새 프로세스의 환경이 명시적으로 지정되고 다른 경우에는 호출 프로세스에서 상속된다는 것입니다.

호출 프로세스에서 열린 파일은 "실행 시 닫기" 플래그가 설정된 파일을 제외하고 새 프로세스에서 열린 상태로 유지됩니다. fcntl(2) ]. 파일이 열려 있으면 파일의 현재 위치에 대한 포인터가 유지됩니다.

호출 프로세스에서 가로채어진 신호로 인해 새 프로세스가 종료된다는 점을 제외하고 신호에 대한 응답은 보존됩니다. 신호(2) ].

sigset(2)를 호출하여 신호 응답을 설정하고 SIG_DFL, SIG_IGN 또는 SIG_HOLD로 지정한 경우 이 응답은 호출 프로세스에서 상속됩니다. 그러나 신호가 차단되면 SIG_DFL 응답이 설정되고 이 유형의 수신되었지만 처리되지 않은 모든 신호는 따로 보관됩니다.

새 실행 파일에 현재 사용자 ID를 재설정하도록 권한 비트가 설정된 경우 [참조 chmod(2) ], 새 프로세스의 유효 사용자 ID는 새 실행 파일의 소유자 ID로 설정됩니다. 마찬가지로, 새 실행 파일의 권한 비트가 유효 그룹 ID를 재설정하도록 설정된 경우 새 프로세스의 유효 그룹 ID는 새 실행 파일의 그룹 ID로 설정됩니다. 새 프로세스의 실제 사용자 ID와 실제 그룹 ID는 호출 프로세스에서 상속됩니다.

연결된 공유 메모리 세그먼트는 새 프로세스에 의해 상속되지 않습니다. shmop(2) ].

새 프로세스에서 프로파일링이 비활성화되었습니다.

또한 새 프로세스는 exec를 호출한 프로세스에서 다음 특성을 상속합니다.

  1. 우선순위 조정 값 [참조 좋아요(2) ].
  2. 프로세스 ID.
  3. 상위 프로세스의 ID입니다.
  4. 프로세스 그룹 ID.
  5. semadj 의미 [참조 semop(2) ].
  6. 터미널 그룹 ID [참조 종료(2) ].
  7. 추적 모드 [참조 ptrace(2) ].
  8. 알람이 울릴 때까지 남은 시간 [참조 알람(2) ].
  9. 현재 작업 디렉토리.
  10. 루트 디렉토리.
  11. 파일 생성 모드 마스크 [참조. umask(2) ].
  12. 파일 크기 제한 [참조 ulimit(2) ].
  13. 이 프로세스를 서비스하는 데 소요된 시간 카운터(tms_utime, tms_stime, tms_cutime, tms_cstime) [참조 회(2)].
  14. 파일 세그먼트에 대한 액세스 차단 [참조. fcntl(2) 및 lockf(3C) ].

다음 조건 중 하나 이상이 참일 경우 exec 시스템 호출이 실패하고 제어가 반환됩니다. 새 실행 파일의 경로 구성 요소가 존재하지 않습니다. 새 실행 파일의 경로 구성 요소가 디렉터리가 아닙니다. 새 실행 파일의 경로에 나열된 디렉터리 중 하나를 볼 수 없습니다. 새 실행 파일은 일반 파일이 아닙니다. 새 파일을 실행할 수 있는 권한이 없습니다. 새 파일에 실행 권한이 있지만 헤더가 유효한 매직 번호로 시작하지 않습니다. [참조 a.out(4)]. 새 실행 파일이 현재 일부 프로세스에서 쓰기 위해 열려 있습니다. 새로운 프로세스에는 다음이 필요합니다. 더 많은 메모리, MAXMEM이 허용하는 시스템 제한보다 높습니다. 인수 목록의 전체 길이가 시스템 제한인 5120바이트를 초과합니다. 필수 장비가 누락되었습니다. 인수로 사용할 수 없는 주소입니다. 메모리가 부족합니다. 필수 공유 라이브러리에는 실행 액세스가 허용되지 않습니다. 공유 라이브러리를 직접 실행하려고 했습니다. Exec 호출 중에 신호가 차단되었습니다. 경로 인수는 다음을 가리킵니다. 원격 컴퓨터, 연결 이 순간아니요. 경로 구성 요소에는 원격 컴퓨터에 대한 여러 호출이 필요합니다.

int execle(char *fname, char *arg0, ..., char *argN, NULL, char *envp)

int execlp(char *fname, char *arg0, ..., char *argN, NULL)

int execlpe(char *fname, char *arg0, ..., char *argN, NULL, char *envp)

int execv(char *fname, char *arg)

int execve(char *fname, char *arg, char *envp)

int execvp(char *fname, char *arg)

int execvpe(char *fname, char *arg, char *envp)

설명:

이러한 함수는 ANSI C 표준에 정의되어 있지 않습니다.

기능 그룹 간부다른 프로그램을 실행하는데 사용됩니다. 하위 프로세스라고 불리는 이 다른 프로그램은 exec 호출이 포함된 프로그램 위에 로드됩니다. 하위 프로세스를 포함하는 파일 이름은 fname 매개변수를 사용하여 지정됩니다. 하위 프로세스에 전달된 모든 인수는 매개변수 arg0에서 argN까지 또는 배열 arg에 의해 지정됩니다. envp 매개변수는 환경 문자열을 가리켜야 합니다. (자식 프로세스에서 argv가 가리키는 인수입니다.)

fname에 확장자나 점이 포함되어 있지 않으면 먼저 파일 이름으로 검색이 수행됩니다. 실패하면 EXE 확장자가 추가되고 검색이 반복됩니다. 실패하면 COM 확장이 사용되며 검색이 다시 반복됩니다. 확장자를 지정하면 해당 제목에 대해서만 검색이 수행됩니다. 정확히 일치. 마지막으로 마침표가 있지만 확장자를 지정하지 않은 경우 파일 이름 왼쪽에서 검색이 수행됩니다.

하위 프로세스가 실행되는 정확한 방법은 호출되는 exec 함수의 버전에 따라 다릅니다. exec 함수에는 해당 작업을 지정하는 다양한 접미사가 있는 것으로 생각할 수 있습니다. 접미사는 하나 또는 두 개의 문자로 구성될 수 있습니다.

p 접미사가 붙은 함수는 PATH 명령으로 지정된 디렉터리에서 하위 프로세스를 찾습니다. p 접미사가 없으면 현재 디렉토리에서만 검색이 수행됩니다.

l 접미사가 지정되면 인수가 배열이 아닌 개별적으로 자식 프로세스에 전달된다는 의미입니다. 이 방법은 고정된 수의 인수를 전달할 때 사용됩니다. 마지막 인수는 NULL이어야 합니다. (NULL은 다음에 정의되어 있습니다. stdio.h.)

v 접미사는 인수가 배열의 하위 프로세스에 전달됨을 의미합니다. 이 방법은 자식 프로세스에 인수가 몇 개나 전달될지 미리 알 수 없거나, 프로그램 실행 중에 인수 개수가 변경될 수 있는 경우에 사용됩니다. 일반적으로 배열의 끝은 널 포인터로 표시됩니다.

e 접미사는 하나 이상의 환경 문자열이 하위 프로세스에 전달됨을 나타냅니다. envp 매개변수는 문자열에 대한 포인터 배열입니다. 배열이 가리키는 각 줄은 다음과 같아야 합니다. 환경_변수 = 값

배열의 마지막 포인터는 NULL이어야 합니다. 배열의 첫 번째 요소가 NULL이면 하위 프로세스는 상위 프로세스와 동일한 환경을 유지합니다.

exec로 연 파일은 하위 프로그램에서도 열린다는 점을 기억하는 것이 중요합니다.

성공하면 exec는 값을 반환하지 않습니다. 실패하면 -1이 반환되고 errno는 다음 값 중 하나로 설정됩니다.

첫 번째 다음 프로그램인수를 인쇄하는 두 번째 것을 호출합니다. 두 프로그램 모두에 있어야 합니다. 별도의 파일.

/* 첫 번째 파일은 부모 파일입니다 */
#포함하다
#포함하다
#포함하다
정수 메인(공허)
{
execl("test.exe" , "test.exe" , "hello" , "10" , NULL) ;
0을 반환합니다;
}

/* 두 번째 파일은 하위 파일입니다 */
#포함하다
#포함하다
int main(int argc, char * argv)
{
인쇄( "이 프로그램은 다음 명령줄로 실행됩니다.") ;
printf("인수: " ) ;
printf(argv[ 1 ] ) ;
printf(" %d" , atoi (argv[ 2 ] ) ) ;
0을 반환합니다;
}

분석 수명주기프로세스가 시작됨 운영 체제유닉스

시스템 관리자의 많은 책임 중 하나는 사용자 프로그램이 올바르게 시작되는지 확인하는 것입니다. 이 작업은 시스템에서 동시에 실행되는 다른 프로그램으로 인해 복잡해집니다. 에 의해 여러가지 이유이러한 프로그램은 작동하지 않거나 정지되거나 결함이 있을 수 있습니다. UNIX® 운영 체제에서 이러한 작업을 생성, 관리 및 삭제하는 프로세스를 이해하는 것은 가장 중요한 단계더욱 신뢰할 수 있는 시스템을 만드는데 힘쓰겠습니다.

개발자는 시스템의 다른 부분과 잘 작동하는 응용 프로그램이 더 적은 리소스를 필요로 하고 시스템에 문제를 일으킬 가능성이 적기 때문에 커널이 프로세스를 관리하는 방법을 연구합니다. 시스템 관리자. 좀비 프로세스(나중에 설명)를 생성하기 때문에 지속적으로 다시 시작해야 하는 애플리케이션은 당연히 바람직하지 않습니다. 이해 유닉스 시스템관리되는 프로세스를 통해 개발자는 원활하게 실행되는 프로그램을 만들 수 있습니다. 배경. 다른 사람의 화면에 터미널 세션을 표시할 필요가 없습니다.

이러한 프로그램의 주요 제어 구성 요소는 프로세스입니다. 프로세스는 운영 체제에서 실행되는 프로그램에 부여되는 이름입니다. ps 명령을 알고 있다면 .

목록1. ps 명령의 출력
sunbox#ps -ef UID PID PPID C STIME TTY TIME CMD 루트 0 0 0 20:15:23 ? 0:14 sched 루트 1 0 0 20:15:24 ? 0:00 /sbin/init 루트 2 0 0 20:15:24 ? 0:00 페이지아웃 루트 3 0 0 20:15:24 ? 0:00 fsflush 데몬 240 1 0 20:16:37 ? 0:00 /usr/lib/nfs/statd ...

처음 세 열은 고려해야 할 중요한 사항입니다. 첫 번째에는 프로세스를 대신하여 실행 중인 사용자 목록이 포함되어 있고, 두 번째에는 프로세스의 ID가 나열되어 있으며, 세 번째에는 상위 프로세스의 ID가 포함되어 있습니다. 마지막 열에는 프로세스에 대한 설명(일반적으로 이름)이 포함됩니다. 실행 중인 프로그램. 각 프로세스에는 프로세스 식별자(PID)라는 식별자가 할당됩니다. 프로세스에는 상위 프로세스도 있으며, 대부분의 경우 해당 프로세스를 시작한 프로세스의 PID가 표시됩니다.

상위 PID(PPID)가 존재한다는 것은 한 프로세스가 다른 프로세스에 의해 생성되었음을 의미합니다. 시스템에서 시작되는 초기 프로세스를 init라고 하며 항상 PID 1이 할당됩니다. init는 부팅 시 커널이 시작하는 첫 번째 실제 프로세스입니다. init의 주요 임무는 전체 시스템을 시작하는 것입니다. init 및 PPID 0의 기타 프로세스는 커널 프로세스입니다.

포크 시스템 호출 사용

fork(2) 시스템 호출은 새로운 프로세스를 생성합니다. 에 사용된 포크는 다음과 같습니다. 간단한 예 C 코드.

목록 2. 포크의 간단한 사용(2)
sunbox$ 고양이 포크1.c #include #포함하다 int main (void) ( pid_t p; /* 포크는 pid_t 유형을 반환합니다 */ p = fork(); printf("fork는 %d\n을 반환했습니다", p); ) sunbox$ gcc fork1.c -o fork1 sunbox$ . /fork1 포크가 0을 반환했습니다. 포크가 698을 반환했습니다.

fork1.c의 코드는 단순히 포크를 호출하고 printf 호출을 통해 포크를 실행한 정수 결과를 표시합니다. 호출은 하나만 이루어지지만 출력은 두 번 표시됩니다. 이는 새로운 프로세스가 생성되었기 때문에 발생합니다. 포크를 부르다. 호출 후 두 개의 별도 프로세스가 반환됩니다. 이를 종종 "한 번 호출하고 두 번 반환"이라고 합니다.

포크의 반환 값은 매우 흥미롭습니다. 그 중 하나는 0입니다. 다른 하나는 0이 아닌 값입니다. 0을 받는 프로세스를 호출합니다. 프로세스에 의해 생성된, 0이 아닌 값은 원래 프로세스로 이동합니다. 상위 프로세스. 반환 값을 사용하여 어떤 프로세스가 어떤 프로세스인지 결정합니다. 두 프로세스 모두 동일한 영역에서 실행을 재개하므로 유일한 차별화 요소는 fork 의 반환 값입니다.

null 및 null이 아닌 반환 값의 이론적 근거는 자식 프로세스가 항상 getppid(2) 요청을 사용하여 부모를 알아낼 수 있지만 부모가 모든 자식을 결정하는 것이 훨씬 더 어렵다는 것입니다. 이런 방식으로 부모는 새로운 자식에 대해 알게 되고, 필요한 경우 자식은 부모를 찾을 수 있습니다.

이제 fork 의 반환 값을 알면 코드는 하위 프로세스와 상위 프로세스를 구별하고 그에 따라 동작할 수 있습니다. B는 다음을 표시하는 프로그램을 보여줍니다. 다른 결론, fork 결과를 기반으로 합니다.

목록 3. 추가 정보 완전한 예 포크를 사용하여
sunbox$ 고양이 포크2.c #include #포함하다 int main (void) ( pid_t p; printf("원래 프로그램, pid=%d\n", getpid()); p = fork(); if (p == 0) ( printf("자식 프로세스에서 pid =%d, ppid=%d\n", getpid(), getppid()); ) else ( printf("부모에서 pid=%d, 포크가 반환됨=%d\n", getpid(), p) ; ) ) sunbox$ gcc fork2.c -o fork2 sunbox$ ./fork2 원본 프로그램, pid=767 하위 프로세스에서 pid=768, ppid=767 상위 프로세스에서 pid=767, 포크 반환=768
목록 6. 상위 프로세스가 하위 프로세스보다 먼저 종료됩니다.
#포함하다 #포함하다 int main(void) ( int i; if (fork()) ( /* 부모 */ sleep(2); _exit(0); ) for (i=0; i< 5; i++) { printf("My parent is %d\n", getppid()); sleep(1); } } sunbox$ gcc die1.c -o die1 sunbox$ ./die1 My parent is 2920 My parent is 2920 sunbox$ My parent is 1 My parent is 1 My parent is 1

이 예에서 상위 프로세스는 포크를 호출하고 2초를 기다린 후 종료됩니다. 자식 프로세스는 계속해서 부모의 PID를 5초 동안 인쇄합니다. 부모가 죽으면 PPID가 1로 변경되는 것을 볼 수 있습니다. 또한 흥미로운 점은 제어권 반환입니다. 명령 프로세서. 하위 프로세스는 백그라운드에서 실행되므로 상위 프로세스가 종료되면 제어권이 셸로 반환됩니다.

아이가 부모보다 먼저 죽는다

반대 과정, 즉 부모보다 후손이 먼저 죽는 과정을 설명합니다. 무슨 일이 일어나고 있는지 더 잘 보여주기 위해 프로세스에서 직접 인쇄되는 것은 없습니다. 이것 대신에, 흥미로운 정보프로세스 목록에 있습니다.

목록 7. 하위 프로세스는 상위 프로세스보다 먼저 종료됩니다.
sunbox$ 고양이 die2.c #include #포함하다 int main(void) ( int i; if (!fork()) ( /* 자식은 즉시 종료됩니다*/ _exit(0); ) /* 부모는 약 1분 정도 기다립니다 */ sleep(60); ) sunbox$ gcc die2. c -o die2 sunbox$ ./die2 & 2934 sunbox$ ps -ef | grep 2934 sean 2934 2885 0 21:43:05 pts/1 0:00 ./die2 sean 2935 2934 0 - ? 0:00 sunbox$ ps -ef | grep 2934 + 종료 199 ./die2

die2는 & 연산자를 사용하여 백그라운드에서 실행된 다음 프로세스 목록을 표시하고 실행 중인 프로세스와 해당 하위 프로세스만 표시합니다. PID 2934는 상위 프로세스이고, PID 2935는 생성되어 즉시 종료되는 프로세스입니다. 조기 종료에도 불구하고 생성된 프로세스는 여전히 프로세스 테이블에 있습니다. 죽은프로세스라고도 함 좀비. 60초 후에 상위 프로세스가 종료되면 두 프로세스가 모두 종료됩니다.

하위 프로세스가 종료되면 해당 상위 프로세스는 SIGCHLD라는 신호를 사용하여 이를 알립니다. 이 모든 것의 정확한 메커니즘은 지금은 중요하지 않습니다. 정말 중요한 것은 부모가 아이의 죽음에 대해 어떻게든 알고 있어야 한다는 것입니다. 아이가 죽는 순간부터 부모가 신호를 받을 때까지 아이는 좀비 상태에 있다. 좀비는 실행되지 않으며 CPU 리소스를 소비하지 않습니다. 프로세스 테이블의 공간만 차지합니다. 부모가 죽으면 커널은 마침내 부모와 함께 자식도 제거할 수 있습니다. 수단, 유일한 방법좀비를 제거하는 것은 부모를 죽이는 것입니다. 가장 좋은 방법좀비를 처리하세요. 좀비가 위로 나오지 않도록 하세요. 코드 B는 들어오는 SIGCHLD 신호를 처리하는 신호 처리기를 설명합니다.

목록 8. 작동 중인 신호 핸들러
#포함하다 #포함하다 #포함하다 #포함하다 void singandler(int sig) ( printf("신호 %d에 대한 신호 처리기\n", sig); /* wait()는 SIGCHLD를 확인하기 위한 주요 것입니다 */ wait(0); ) int main(void) ( int i ; SIGCHLD로 신호 처리기 설정 */ sigset(SIGCHLD, &sighandler); if (!fork()) ( /* Descendant */ _exit(0); ) sleep(60); 3116 sunbox$ 신호 18 ps -ef에 대한 신호 처리기 | grep 3116 sean 3116 2885 0 22:37:26 pts/1 0:00 ./die3

함수 포인터를 신호 처리기로 설정하는 sigset 함수가 있기 때문에 이전 예제보다 조금 더 복잡합니다. 프로세스가 처리된 신호를 수신할 때마다 sigset을 통해 지정된 함수가 호출됩니다. SIGCHLD 신호의 경우 응용 프로그램은 wait(3c)를 호출하여 하위 프로세스가 종료될 때까지 기다려야 합니다. 프로세스가 이미 완료되었으므로 커널이 자식이 사망했다는 확인을 받는 데 필요합니다. 사실, 부모는 단순히 신호를 인정하는 것 이상의 일을 해야 합니다. 그는 또한 자녀의 데이터를 정리해야 합니다.

die3이 실행된 후 프로세스 목록을 확인합니다. 신호 처리기는 값 18(SIGCHLD)을 수신하고 자식의 완료 확인이 이루어지며 부모는 sleep(60) 수면 상태로 돌아갑니다.

간략한 결론

UNIX 프로세스는 하나의 프로세스가 실행 중인 프로세스를 둘로 분할하는 포크를 호출할 때 생성됩니다. 그런 다음 프로세스는 현재 이미지를 새 이미지로 바꾸는 exec 계열의 시스템 호출 중 하나를 실행할 수 있습니다.

부모 프로세스가 죽으면 모든 자식은 PID가 1인 init에 의해 채택됩니다. 자식이 부모보다 먼저 죽으면 신호가 부모 프로세스에 전달되고 자식은 신호가 나올 때까지 좀비 상태가 됩니다. 승인되거나 상위 프로세스가 종료됩니다.

이제 프로세스가 어떻게 생성되고 삭제되는지 알았으므로 시스템에서 실행되는 프로세스를 더 쉽게 이해할 수 있습니다. 이는 특히 다음과 같은 프로세스의 경우에 해당됩니다. 복잡한 구조, 이는 Apache와 같은 여러 다른 프로세스로 인해 복잡해집니다. 일부 프로세스 트리를 추적하는 기능 별도의 프로세스, 모든 애플리케이션을 프로세스까지 역추적할 수 있습니다.



질문이 있으신가요?

오타 신고

편집자에게 전송될 텍스트: