Search

[Linux] ps 와 proc

Tags

개요

이번 포스팅에서는 리눅스의 ps 명령어와 이를 이해하기 위한 proc 디렉터리에 대해서 알아보겠습니다. 더 나아가 ps 기능과 유사한 커스텀 ps를 만들어보는 과정도 함께 정리해보도록 하겠습니다.

1. ps란?

2. proc이란?

리눅스 루트 디렉터리 바로 아래에 있는 폴더입니다.
/proc 이동 후 다음 명령어를 치면 PID 와 같은 이름의 폴더들이 표시됩니다.
/proc 의 파일 구조는 다음과 같습니다.
/proc/[PID]/ -+-- cmdline | +-- cwd | +-- environ | +-- exe | +-- fd -------+-- 0 | | +-- maps +-- 1 | +-- root | +-- stat | +-- statm | +-- status
Java
복사
구성 목록은 다음과 같습니다.
디렉터리
설명
/proc/[PID]/maps
프로세스가 mapping 된 메모리 주소 공간. 모든 프로세스에는 각자 주소 공간이 있으며, 이 주소 공간은 가상 메모리 관리자(VMM)가 제공하고 관리
/proc/[PID]/cmdline
프로세스 인수(argv) 전체를 포함. Command Line에서 넘어온 argumnet를 포함하여 프로세스가 질생된 방식을 정확하고 신속하게 파악하는 수단으로 사용
/proc/[PID]/coredump_filter
메모리 유형의 비트마스크를 포함하며 프로세스의 어떤 메모리 세그먼트를 덤프시킬 것인지 설정
/proc/[PID]/cwd/
프로세스가 사용중인 디렉토리나 파일
/proc/[PID]/environ
프로세스의 현재 환경을 저장. 프로세스 map에서 가장 아랫부분, 즉 커널이 프로세스 환경 정보를 저장하는 메모리 위치를 직접 가리키는 링크
/proc/[PID]/exe
실행중인 프로그램 이름
/proc/[PID]/fd/proc/[PID]/fdinfo
프로세스가 사용중인 File Descriptor 링크와 정보 저장
/proc/[PID]/limits
프로세스에 적용된 resource 제한 사항
/proc/[PID]/loginuid
해당 프로세스를 실행하는 login UID
/proc/[PID]/mem
프로세스가 사용중인 메모리 상태
/proc/uptime
시스템 가동 시간에 대한 정보를 기록한다.
/proc/meminfo
물리적 메모리 및 스왑 메모리 정보가 들어 있는 파일이다.
/proc/cmdline
부팅 시에 실행되는 커널 관련 옵션에 대한 정보를 담고 있다.
/proc/loadavg
최근 1분, 5분. 15분 동안의 평균 부하율을 기록하는 파일이다.
/proc/modules
현재 모듈로 로딩된 모듈 목록, lsmod 했을때 나오는 정보
/proc/mounts
마운트된 파일시스템에 대한 정보
/proc/partitions
현재 시스템의 파티션 정보
/proc/stat
CPU, 인터럽트, 컨텍스트 스위치 등 일반적인 시스템 통계 정보

3. 나만의 ps 만들기

과제에는 다음과 같은 조건이 주어집니다.
System 함수랑 Exec 계열 함수를 사용하지 않고 코드를 구현해야 한다.
우선 C에서 system 함수의 종류에 대해 알고 있어야 합니다.
system 함수는 <stdlib.h> 헤더 파일에 정의되어 있습니다.
예를 들어 ps 명령어는 다음과 같은 코드로 실행 가능합니다.
#include <stdlib.h> #include <stdio.h> int main () { int result = system("ps"); if(result == -1) { perror("system"); } return 0; }
Java
복사
exec 도 마찬가지입니다. 이를 방지하기 위해서는 직접 /proc 파일 시스템을 읽어서 프로세스의 정보를 얻을 필요가 있었습니다.
 직접 /proc 파일을 어떻게 읽는건데?
C 언어에서 디렉토리를 여는 것은 opendir()을 사용합니다. opendir() 은 dirent.h에 포함되어 있는 함수인데요, 이는 특정 디렉토리에 포함된 디렉토리 명 또는 파일 명을 읽어야 하는 경우에 사용합니다.
opendir()은 디렉터리 스트림을 참조하는 포인터를 반환합니다.
예를 들어, DIR *dr_ptr = opendir(”/proc”); 이란 코드를 실행했을 때, 해당 포인터를 사용하여 readdir() 함수를 호출하면 디렉터리 내의 각 엔트리에 대한 정보를 담고 있는 struct dirent 형식의 포인터를 반환받을 수 있습니다.
 struct dirent 형식에는 어떤 속성들이 있는데?
struct dirent { ino_t d_ino; /* inode 번호 (파일이나 디렉터리의 식별자) */ off_t d_off; /* 다음 디렉터리 엔트리까지의 오프셋 */ unsigned short d_reclen; /* 레코드의 길이 */ unsigned char d_type; /* 파일의 타입 */ char d_name[256]; /* 디렉터리의 이름 */ };
Java
복사
ino_toff_t에 대한 설명은 다음과 같습니다.
ino_t: inode 번호를 표현하는데 사용되는 데이터 타입입니다. inode는 유닉스와 유닉스 계열의 파일 시스템에서 파일이나 디렉터리를 고유하게 식별하는 식별자입니다. ino_t는 일반적으로 부호 없는 정수 형식입니다.
off_t: 파일 오프셋이나 파일 크기를 나타내는 데 사용되는 데이터 타입입니다. 일반적으로 off_t는 정수 타입으로, 파일 내에서의 위치나 파일의 길이를 바이트 단위로 표현할 수 있게 해줍니다.
위의 내용들을 바탕으로 /proc을 분석해보자
/proc에는 다음과 같은 디렉터리들이 존재합니다.
숫자로 된 디렉터리는 프로세스 ID 즉, PID를 의미합니다. 각 디렉터리 안에는 해당 프로세스에 대한 자세한 정보가 담겨 있습니다.
문자로 된 디렉터리에는 시스템 자체 정보, 커널 설정 등의 정보가 담겨 있습니다.
더 구체적인 /proc 디렉터리의 구조는 다음과 같습니다.
/proc/[PID]/ -+-- cmdline | +-- cwd | +-- environ | +-- exe | +-- fd -------+-- 0 | | +-- maps +-- 1 | +-- root | +-- stat | +-- statm | +-- status
Java
복사
이러한 디렉터리 구조에서 ps 명령어를 입력했을 때 필요한 정보들을 찾아야 합니다.
ps 명령 시 다음과 같은 정보들이 필요합니다.
PID (Process ID) : /proc/[PID]/
TTY (Controlling Terminal) : /proc/[PID]/stat 에 포함되어 있습니다.
TIME (CPU Time) : /proc/[PID]/stat 에 포함되어 있습니다.
CMD (Command Line) : /proc/[PID]/cmdline 에 포함되어 있습니다.
cmdline 파일에 저장된 내용을 출력하면 됩니다.
PID와 CMD 같은 경우 디렉터리의 이름 혹은 파일의 내용을 읽으면 되지만 stat 파일 내에 있는 내용은 조금의 분석이 필요합니다.
stat 파일에는 프로세스에 대한 다양한 정보가 공백으로 구분되어 있는데, 각 필드는 정해진 순서대로 정보를 나열하고 있습니다.
그 내용은 다음과 같습니다.
1.
pid: 프로세스 ID.
2.
comm: 실행 중인 명령의 이름 (이름 안에 공백이 있으면 괄호로 묶여 있습니다).
3.
state: 프로세스 상태 (R: 실행 중, S: 대기 중, D: 디스크 대기 중, Z: 좀비 상태, T: 중지됨, 등).
4.
ppid: 부모 프로세스 ID.
5.
pgrp: 프로세스 그룹 ID.
6.
session: 세션 ID.
7.
tty_nr: 프로세스가 제어하는 터미널의 ID.
8.
tpgid: 포그라운드 프로세스 그룹 ID.
9.
flags: 커널 플래그 값.
10.
minflt: 프로세스가 경험한 소폐면(page fault)의 수.
11.
cminflt: 자식 프로세스가 경험한 소폐면의 수.
12.
majflt: 프로세스가 경험한 큰폐면의 수.
13.
cmajflt: 자식 프로세스가 경험한 큰폐면의 수.
14.
utime: 유저 모드에서 CPU가 소비한 시간 (초).
15.
stime: 커널 모드에서 CPU가 소비한 시간 (초).
16.
cutime: 자식 프로세스가 유저 모드에서 CPU가 소비한 시간의 누적 값.
17.
cstime: 자식 프로세스가 커널 모드에서 CPU가 소비한 시간의 누적 값.
18.
priority: 프로세스 우선순위 (낮은 값이 높은 우선순위).
19.
nice: 프로세스의 "nice" 값 (프로세스 스케줄링 우선순위에 영향을 줌).
20.
num_threads: 프로세스에서 실행 중인 스레드의 수.
21.
itrealvalue: 시간에 따른 타이머 감소값 (현재 무시됨).
22.
starttime: 부팅 이후 프로세스 시작 시간 (jiffies 단위).
23.
vsize: 가상 메모리 크기 (바이트).
24.
rss: Resident Set Size - 물리 메모리에서의 프로세스의 크기 (페이지 단위). ... (이어지는 필드들은 다양한 추가 정보를 담고 있습니다)
여기서 우리가 얻어야 하는 정보는 7번째, 14번째 와 15번째에 위치하고 있습니다.
ttr 정보를 얻는 법은 복잡하지만 코드로 나타내면 다음과 같습니다.
char tty_path[256]; if (major == 136 || (major == 4 && minor >= 64)) { sprintf(tty_path, "/dev/pts/%u", minor); } else if (major == 4 && minor < 64) { sprintf(tty_path, "/dev/tty%u", minor); } else { strcpy(tty_path, "unknown"); }
Java
복사
 그렇다면 코드를 어떻게 구상해야될까요?
opendir() 함수를 이용해서 /proc 디렉터리에 있는 디렉터리 목록을 확인합니다. 숫자로 된 디렉터리는 PID를 나타내고 해당 디렉터리 내에는 많은 정보가 포함되어 있습니다.
/proc/[PID] 디렉터리 내에 있는 정보들을 꺼내옵니다. 필요한 정보들은 PID, TTY, TIME, CMDLINE 입니다.
PID와 CMDLINE은 디렉터리 이름 혹은 파일의 내용을 통해 쉽게 추출할 수 있습니다.
TTY와 TIME은 stat 파일의 내용을 분석하여 출력합니다. 이를 위해 별도의 분석 함수가 필요합니다.
마지막으로 이를 ps 명령어와 동일하게 출력하도록 포매팅을 합니다.
코드
#include <stdio.h> #include <dirent.h> #include <ctype.h> #include <string.h> int main() { struct dirent *de; DIR *dr = opendir("/proc"); if (dr == NULL) { printf("Could not open /proc directory\n"); return 0; } while ((de = readdir(dr)) != NULL) { if (de->d_type == DT_DIR && isdigit(de->d_name[0])) { char filepath[256]; char buffer[1024]; // Getting the process command line arguments sprintf(filepath, "/proc/%s/cmdline", de->d_name); FILE *f = fopen(filepath, "r"); if (f) { fgets(buffer, sizeof(buffer), f); fclose(f); // Replace NULL characters with spaces for(int i = 0; i < strlen(buffer); i++) { if(buffer[i] == '\0') { buffer[i] = ' '; } } printf("PID: %s, CMDLINE: %s\n", de->d_name, buffer); } } } closedir(dr); return 0; }
Java
복사
dirent 구조체는 다음과 같다
struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* not an offset; see below */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all filesystem types */ char d_name[256]; /* filename */ };
Java
복사
컴파일 하는 법
gcc -o newps newps.c ./newps
Java
복사

5. 번외

로컬에서 ubuntu로 파일 전송하는 방법은 다음과 같습니다.
1.
ssh가 있는지 파악한다
ps -ef | grep sshd
Java
복사
2.
ip 주소를 파악한다
ip addr show
Java
복사
192.168.64.5
3.
scp 명령어를 실행한다
scp local 파일 경로 ubuntu@192.168.64.5
Java
복사

4. Reference