티스토리 뷰
창병모, 리눅스 프로그래밍 원리와 실제(생능출판, 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-영역 : 열린 파일 디스크립터, 현재 작업 디렉터리 등과 같은 프로세스의 정보 저장
'❄️ Sookmyung > 💾 Linux' 카테고리의 다른 글
[Linux Programming] Chapter 7. 파일 및 레코드 잠금 (0) | 2022.04.25 |
---|---|
[Linux Programming] Chapter 6. 파일 시스템 (0) | 2022.04.25 |
[Linux Programming] Chapter 5. 파일 입출력 (0) | 2022.04.25 |
[Linux Programming] Chapter 4. C 표준 파일 입출력 (0) | 2022.04.22 |
[Linux Programming] Chapter 3. C 프로그래밍 환경 (0) | 2022.04.21 |