티스토리 뷰

창병모, 리눅스 프로그래밍 원리와 실제(생능출판, 2022)

8.1 쉘과 프로세스

쉘 : 사용자와 운영체제 사이에 창구 역할을 하는 소프트웨어

- 사용자로부터 명령어를 입력받아 이를 처리하는 명령어 처리기(command processor) 역할 수행

- 쉘은 사용자와 운영체제 사이에 창구 역할을 하는 소프트웨어로 사용자로부터 명령어를 입력받아 이를 처리

 

명령어 실행

- 쉘은 사용자가 입력한 명령어를 실행하기 위해 새로운 자식 프로세스를 생성해 입력한 명령어 실행

- 자식 프로세스가 입력된 명령어를 실행하고, 쉘은 자식 프로세스 실행을 끝날 때까지 기다림

- 자식 프로세스 실행이 끝나면 기다렸다가 다시 쉘 프롬프트 출력하고 다음 명령어 기다림

 

명령어 열(command sequence) : 여러 명령어 순차적으로 실행

$ 명령어1; ...; 명령어n

 

명령어 그룹(command group) : 명령어 순차 실행, 하나의 명령어처럼 사용되어 표준입력, 표준출력, 표준오류 공유

$ (명령어1; ...; 명령어n)

➡ 입출력 재지정과 파이프를 사용할 때 하나의 명령어처럼 모든 입출력 재지정 가능

 

전면 처리와 후면 처리

- 명령어를 전면처리하면 하나의 명령어만 실행 가능

- Ctrl-C : 강제 종료

- Ctrl-Z : 명령어 실행 정지

- fg : 정지된 명령어 다시 실행

 

$ 명령어 &

➡ 후면에서 명령 실행

 

$ fg %작업번호

➡ 작업번호에 해당하는 후면 작업을 전면 작업으로 전환

 

후면 처리 예

 

프로세스 리스트 ps

- 프로그램이 실행되면 이를 프로세스, 작업이라고 함

$ ps [-옵션] ➡ 프로세스 상태정보 출력

 

프로세스 제어

- $ sleep 초 : 지정된 시간만큼 프로세스 시간 지연

- $ kill 프로세스 번호(%작업번호) : 실행중인 프로세스 강제 종료

- $ wait [프로세스번호] : 해당 프로세스 번호를 갖는 자식 프로세스가 종료될 때까지 기다림

- $ exit [종료값] : 쉘을 종료하고 종료코드를 부모 프로세스에 전달

 

8.2 프로그램 실행

프로그램 실행 exec()

리눅스에서 프로그램 실행, 종료

- 사용자가 프로그램 실행시키는 방법 두가지 

- 1) 쉘 프롬프트에서 프로그램을 지정하여 실행  2) 실행중인 프로그램(사용자 프로세스) 내에서 exec() 시스템 호출 이용해 다른 프로그램 실행 ➡ 동일한 방법

- 쉘 프로세스도 이미 실행중인 프로세스이며, 실제로 쉘 프로세스 내부에서는 사용자로부터 입력받은 실행할 프로그램을 exec() 시스템 호출에 의해 실행

 

- exec() 시스템 호출은 실행될 프로그램의 시작 루틴에게 명령줄 인수와 환경 변수 전달

- C 프로그램을 컴파일하면 실행 파일에는 C 프로그램 코드와 C 시작 루틴 포함

- 시작 루틴은 exec() 시스템 호출로부터 전달받은 명령줄 인수, 환경 변수 등을 다음과 같이 main() 함수를 호출하면서 main() 함수에 전달

- main() 함수에서부터 사용자가 작성한 프로그램의 실행이 시작되고 결국 main() 함수의 실행이 끝나면 main() 함수의 반환 값을 받아 exit

 

명령줄 인수 argv[]

 

int main(int argc, char *argv[]);

argc : 명령줄 인수의 개수

argv[] : 명령줄 인수 리스트를 나타내는 포인터 배열

- 명령줄 인수 리스트를 나타내는 포인터 배열 argv의 구성은 위와 같음

- argv[0] : 실행 파일 이름, argv[1] : 첫 번째 명령줄 인수

 

환경 변수

- 환경 변수는 쉘이 원래 가지고 있던 것을 쉘이 프로그램을 실행시킬 때 실행되는 프로그램에게 넘겨주게 됨

- 구체적으로는 전역변수 environ을 통해 환경 변수와 값의 리스트를 포인터 배열 형태로 전달받음

 

환경 변수 값 반환 함수 getenv()

 

char *getenv(const char *name)

- 환경 변수 name의 값을 반환, 해당 변수가 없으면 NULL 반환

 

환경 변수 값 설정

 

int putenv(const cahr *name)

- name=value 형태의 스트링을 받아서 이를 환경 변수 리스트에 넣어줌

- 환경 변수 name이 이미 존재하면 원래 값을 새로운 값으로 대체

 

int setenv(const char *name, const char *value, int rewrite);

- 환경 변수 name의 값을 value로 설정

- 환경 변수 name이 이미 존재하는 경우에는 rewrite 값이 0이 아니면 원래 값을 새로운 값으로 대체하고 rewrite 값이 0이면 그대로 둠

 

int unsetenv(const char *name);

- 환경변수 name의 값 지움

 

8.3 프로그램 종료

프로그램 종료 방법

- 정상 종료(normal termination)와 비정상 종료(abnormal termination)로 나뉨

 

정상 종료

- main() 실행을 마치고 반환하면 C 시작 루틴은 이 반환값을 가지고 exit() 호출

- 프로그램 내에서 exit()나 _exit() 호출

 

void exit(int status);

- 뒷정리를 한 후 시스템 호출을 정상적으로 종료, status 값을 종료 코드로 부모 프로세스에게 전달

 

void _exit(int status);

- 뒷정리를 하지 않고 프로세스 즉시 종료

 

비정상 종료

- abort() 시스템 호출은 프로세스에 SIGABRT 시그널을 보내어 프로세스를 비정상적으로 종료

- 시그널에 의한 종료 : 프로세스가 실행 중에 시그널을 받으면 비정상적으로 종료

 

프로그램 시작과 종료 과정

 

(1) 프로그램은 exec() 시스템 호출에 의해 실행 시작

(2) 프로그램은 실행이 시작되면 C 시작 루틴부터 시작되며 이 루틴에서 main() 함수를 호출함으로서 사용자가 작성한 main() 함수의 실행 시작

(3) main() 함수는 시작되면 필요에 따라 사용자 호출, main 함수는 최종적으로 반환함으로써 종료할 수 있으며 이때 반환값이 C 시작 루틴에 전달되고 C 시작 루틴은 이 값을 가지고 exit() 함

(4) main 함수든 사용자 함수든 어떤 함수에서도 직접 exit() 혹은 _exit() 시스템 호출을 할 수 있음

(5) exit() 시스템 호출을 하면 시스템 내의 _exit() 함수가 실행되고 결국 프로그램은 종료되는데 이 함수는 프로그램을 종료하기 전에 표준적인 I/O 뒷정리 수행

(6) _exit() 뒷정리 없음

 

exit 처리기

 

void atexit(void (*func)(void));

➡ exit 처리기로 함수 func를 등록

- exit()에 의한 프로세스 종료 과정에서 자동으로 수행될 exit 처리기(exit handler) 함수 등록 가능

 

8.4 프로세스 ID와 프로세스 사용자 ID

프로세스 ID

- 프로세스 : 실행중인 프로그램

- 각 프로세스는 프로세스를 구별하는 번호인 프로세스 ID를 갖고 있음

- 실행 중인 프로그램 즉 프로세스가 getpid() 시스템 호출을 하면 이 프로세스의 ID 반환

- getppid() 시스템 호출은 부모 프로세스의 ID 반환

pid_t getpid();

➡ 프로세스 ID 반환

 

pid_t getppid();

➡ 부모 프로세스 ID 반환

 

부모 프로세스는? 쉘 프롬프트를 내주고 있는 쉘 프로세스

 

int chdir(char* pathname);

 

➡ 현재 작업 디렉토리를 pathname으로 변경

- 각 프로세스는 현재 작업 디렉터리를 가지고 있음

 

프로세스의 사용자 ID와 그룹 ID

- 프로세스는 프로세스 ID 외의 그 프로세스를 실행시킨 사용자 ID와 그 사용자의 그룹 ID 가짐

- 프로세스 사용자 ID와 그룹 ID는 프로세스가 수행할 수 있는 권한을 검사하는데 사용

- 프로세스의 그룹 ID도 실제 그룹 ID와 유효 그룹 ID라는 두 가지 그룹 ID 사용

 

- 프로세스의 실제 사용자 ID(real user ID)는 그 프로세스를 실행시킨 원래 사용자의 사용자 ID로 설정

- 프로세스의 유효 사용자 ID(effective user ID) : 현재 유효한 사용자 ID로 새로 파일을 만들 때 그 파일의 소유자를 정하거나 파일에 대한 접근 권한 검사할 때 주로 사용

 

프로세스 사용자 ID

 

set-user-id 실행권한

- 프로세스 유효 사용자 ID는 실제 사용자 ID와 동일

- set-user-id(set user ID upon execution)라는 특별한 실행권한이 설정된 실행파일을 실행파일을 실행하면 이 프로세스의 유효 사용자 ID는 그 실행파일의 소유자로 바뀌게 되어 결과적으로 이 프로세스는 실행되는 동안 그 파일의 소유자 권한을 갖게 됨

 

set-user-id 실행권한이 설정된 /bin/passwd 실행 파일을 실행하는 과정

(1) 이 실행파일은 set-user-id 실행권한이 설정된 파일이며 소유자는 root

(2) 일반 사용자 chang이 다음과 같이 이 파일을 실행하게 되면 실행 파일의 소유자인 root가 이 프로세스의 유효 사용자 ID가 됨

- $ /bin/passwd

(3) 이제 이 프로세스는 유효 사용자 ID가 root이므로 root만 수정할 수 있는 암호 파일 /etc/shadow 파일을 접근하여 수정할 수 있음

 

8.5 프로세스 이미지

 

프로세스 이미지 : 프로세스 관리를 위한 커널 내의 프로세스에 대한 정보가 필요, 영역을 위한 메모리를 할당해야 하는데 이러한 메모리 배치를 말함

 

텍스트 : 프로세스가 실행하는 실행코드 저장

데이터 : 전역변수 및 정적 변수를 위한 메모리 영역

힙 : 동적 메모리 할당 위한 영역 (malloc 함수 호출하면 이 영역에서 동적 메모리 할당)

스택 : 함수 호출을 구현하기 위한 실행시간 스택을 위한 영역

U-영역 : 열린 파일 디스크립터, 현재 작업 디렉터리 등과 같은 프로세스의 정보 저장

Comments