[Linux] 프로세스의 일반적인 형식과 주소공간

mommers·2026년 2월 3일

Linux

목록 보기
36/59
post-thumbnail

프로세스 환경 (Process Environment)


목차

  1. Start-up 루틴
  2. 명령줄 인수 (argc, argv)
  3. 환경 변수 (Environment Variables)
  4. 프로세스 메모리 구조
  5. 실전 예제와 주의사항

Start-up 루틴

프로그램 시작 과정

일반적으로 "C 프로그램은 main() 함수에서 시작한다"고 알고 있지만, 실제로는 그 이전에 특별한 start-up 루틴이 존재합니다.

커널 → _start → __libc_start_main → main()

주요 특징

  • 컴파일러가 생성하는 실행 파일에는 linker가 정한 start-up 루틴의 시작 주소가 포함
  • ASLR(Address Space Layout Randomization)을 사용하면 시작 주소를 예측할 수 없음
  • ASLR은 buffer overflow 같은 공격을 방어하기 위한 메모리 보호 장치

ASLR이란?

실행할 때마다 메모리 주소가 바뀜:
- Stack 시작 주소
- Heap 시작 주소
- 라이브러리 로드 주소

→ 공격자가 특정 주소를 예측할 수 없게 만듦

명령줄 인수 (argc, argv)

함수 시그니처

int main(int argc, char *argv[]);
매개변수타입의미
argcint명령줄 인수의 개수
argvchar*[]명령줄 인수 문자열 배열

특징

  • 명령줄 매개변수는 공백(space)으로 구분
  • argv[0]은 항상 프로그램 이름 (실행 경로)
  • argv[argc]는 항상 NULL

예제: cli_args.c

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Number of Arguments Passed: %d\n", argc);
    
    // 모든 명령줄 인수 출력
    for(int i = 0; i < argc; i++) {
        printf("argv[%d]: %s\n", i, argv[i]);
    }
    
    return 0;
}

실행 결과

$ gcc cli_args.c -o cli_args

$ ./cli_args
Number of Arguments Passed: 1
argv[0]: ./cli_args

$ ./cli_args hello world good morning
Number of Arguments Passed: 5
argv[0]: ./cli_args
argv[1]: hello
argv[2]: world
argv[3]: good
argv[4]: morning

환경 변수 (Environment Variables)

환경 변수란?

리눅스 운영체제는 character array 포인터에 저장된 환경 변수 목록을 관리합니다.

형식: name=value
예시: HOME=/home/pi
     PATH=/usr/bin:/bin
     LANG=en_US.UTF-8

environ 변수

extern char **environ;
  • 환경 변수 목록을 참조하는 포인터
  • 사전 정의 변수 + 사용자 생성 변수로 구성
  • 프로그램은 이 환경 변수 목록 하에서 실행됨

환경 변수 목록 보기

예제: env_list.c

#include <stdio.h>

int main() {
    extern char **environ;
    char **env = environ;
    
    // 모든 환경 변수 출력
    while(*env != NULL) {
        printf("%s\n", *env);
        env++;
    }
    
    return 0;
}

실행 결과

$ gcc env_list.c -o env_list
$ ./env_list
SHELL=/bin/bash
PWD=/home/pi/project
LOGNAME=pi
HOME=/home/pi
LANG=C.UTF-8
PATH=/usr/local/bin:/usr/bin:/bin
...

환경 변수 접근 함수

char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);
함수기능반환값
getenv()환경 변수 값 가져오기값 문자열 또는 NULL
setenv()환경 변수 설정/수정성공 0, 실패 -1
unsetenv()환경 변수 삭제성공 0, 실패 -1

예제: env_set_get_del.c

#include <stdio.h>
#include <stdlib.h>

int main() {
    char env_name[15];
    char env_value[255];
    
    printf("Enter Variable name: ");
    scanf("%s", env_name);
    printf("Enter Variable value: ");
    scanf("%s", env_value);
    
    // 환경 변수 설정
    int status = setenv(env_name, env_value, 1);
    
    if(status == 0) {
        printf("Environment variable created successfully!\n");
    } else {
        printf("Environment variable creation failed!\n");
    }
    
    // 값 확인
    printf("%s=%s\n", env_name, getenv(env_name));
    
    // 삭제
    unsetenv(env_name);
    printf("After delete: %s=%s\n", env_name, getenv(env_name));
    
    return 0;
}

실행 결과

$ gcc env_set_get_del.c -o env_test
$ ./env_test
Enter Variable name: MY_VAR
Enter Variable value: hello
Environment variable created successfully!
MY_VAR=hello
After delete: MY_VAR=(null)

$ echo $MY_VAR
                    # 빈 값 (프로세스 내부 변경은 Shell에 영향 없음)

환경 변수 상속 (Inheritance)

핵심 개념

자식 프로세스는 부모의 환경 변수를 물려받지만,
자식이 수정한 내용은 부모에게 영향을 주지 않음

Shell (부모)
  └─ export MY_DATA="Parent_Value"
     └─ ./program (자식)
        └─ setenv("MY_DATA", "Child_Value")  ← 자식만 변경
     
Shell에서 확인 → 여전히 "Parent_Value"

예제: env_inherit.c

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *name = "MY_DATA";
    char *val;
    
    // 1. 부모로부터 상속받은 값 확인
    val = getenv(name);
    if (val == NULL) {
        printf("[Child] '%s' 환경 변수가 없습니다.\n", name);
    } else {
        printf("[Child] 상속받은 값: %s\n", val);
    }
    
    // 2. 자식 프로세스 내에서 값 변경
    printf("[Child] 값을 'Child_Value'로 변경...\n");
    setenv(name, "Child_Value", 1);
    
    // 3. 변경된 값 확인
    printf("[Child] 변경된 값: %s\n", getenv(name));
    printf("[Child] 프로세스 종료.\n");
    
    return 0;
}

실행 및 확인

# 1. 컴파일
$ gcc env_inherit.c -o env_inherit

# 2. 부모(Shell)에서 환경 변수 설정
$ export MY_DATA="Parent_Value"
$ echo $MY_DATA
Parent_Value

# 3. 자식 프로세스 실행
$ ./env_inherit
[Child] 상속받은 값: Parent_Value
[Child] 값을 'Child_Value'로 변경...
[Child] 변경된 값: Child_Value
[Child] 프로세스 종료.

# 4. 부모(Shell) 값 재확인 → 변화 없음!
$ echo $MY_DATA
Parent_Value

결론

  • 상속(Inheritance): 부모 → 자식으로 값 전달됨
  • 격리(Isolation): 자식의 변경사항은 부모에 영향 없음

프로세스 메모리 구조

메모리 레이아웃

리눅스 프로세스는 가상 메모리 공간을 사용하며, ELF 포맷에 따라 구획됩니다.

높은 주소
┌─────────────────┐
│  Command Line   │  ← 명령줄 인수, 환경변수
│  & Environment  │
├─────────────────┤
│     Stack       │  ← 지역변수, 함수 호출
│       ↓         │     (높은 주소 → 낮은 주소)
├─────────────────┤
│                 │
│   (Free Space)  │
│                 │
├─────────────────┤
│       ↑         │
│     Heap        │  ← 동적 할당 (malloc)
│                 │     (낮은 주소 → 높은 주소)
├─────────────────┤
│   BSS Segment   │  ← 초기화 안 된 전역변수
│                 │     (자동으로 0 초기화)
├─────────────────┤
│  Data Segment   │  ← 초기화된 전역변수
│ (Initialized)   │
├─────────────────┤
│  Text Segment   │  ← 실행 코드 + 문자열 리터럴
│    (Code)       │     (Read-Only)
└─────────────────┘
낮은 주소

각 영역 설명

영역내용특징
Text (Code)실행 코드, 문자열 리터럴Read-Only
Data초기화된 전역변수 int a=10;Read-Write
BSS초기화 안 된 전역변수 int b;자동 0 초기화
Heap동적 할당 (malloc)위로 성장 ↑
Stack지역변수, 함수 인자아래로 성장 ↓

실전 예제와 주의사항

❌ 잘못된 코드 예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 문제 1: 문자열 리터럴 (Read-Only)
    char *data = "This is read-only string";
    
    // 문제 2: Heap 메모리 누수
    char *name = malloc(40);
    name = "andrew";  // malloc 주소를 잃어버림!
    
    // 💥 Segmentation Fault!
    strcpy(data, "hello world");  // Read-Only 메모리에 쓰기 시도
    
    return 0;
}

문제 분석

Problem 1: Segmentation Fault

char *data = "This is...";  // data는 Stack, 문자열은 Text(Read-Only)
strcpy(data, "hello");      // ❌ Read-Only 영역에 쓰기 → Crash!

해결책:

// 방법 1: 배열로 선언 (Stack에 복사본 생성)
char data[] = "This is...";

// 방법 2: Heap에 할당
char *data = malloc(50);
strcpy(data, "This is...");

Problem 2: Memory Leak

char *name = malloc(40);    // Heap에 40바이트 할당 (주소: 0x12345678)
name = "andrew";            // Text 영역 주소(0x00400500)로 덮어씀
                            // → 0x12345678은 영원히 접근 불가 (Leak!)

해결책:

char *name = malloc(40);
strcpy(name, "andrew");     // malloc 받은 메모리에 복사
// 사용 후
free(name);                 // 반드시 해제!

✅ 올바른 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // Stack에 배열 할당 (수정 가능)
    char data[100] = "Original string";
    
    // Heap에 메모리 할당
    char *name = malloc(40);
    if (name == NULL) {
        perror("malloc failed");
        return 1;
    }
    
    // 안전하게 복사
    strcpy(name, "andrew");
    strcpy(data, "hello world");
    
    printf("data: %s\n", data);
    printf("name: %s\n", name);
    
    // 메모리 해제
    free(name);
    
    return 0;
}

메모리 영역별 변수 예제

#include <stdio.h>
#include <stdlib.h>

// Data Segment (초기화된 전역변수)
int global_init = 10;

// BSS Segment (초기화 안 된 전역변수)
int global_uninit;

int main() {
    // Stack (지역변수)
    int local = 20;
    
    // Heap (동적 할당)
    int *heap_var = malloc(sizeof(int));
    *heap_var = 30;
    
    // Text (문자열 리터럴)
    char *str = "Hello";
    
    printf("=== 메모리 주소 ===\n");
    printf("Text (str):          %p\n", (void*)str);
    printf("Data (global_init):  %p\n", (void*)&global_init);
    printf("BSS (global_uninit): %p\n", (void*)&global_uninit);
    printf("Heap (heap_var):     %p\n", (void*)heap_var);
    printf("Stack (local):       %p\n", (void*)&local);
    
    free(heap_var);
    return 0;
}

예상 출력 (주소는 매번 다름)

=== 메모리 주소 ===
Text (str):          0x0000000000400610  (낮은 주소)
Data (global_init):  0x0000000000601030
BSS (global_uninit): 0x0000000000601038
Heap (heap_var):     0x0000000001a4a010
Stack (local):       0x00007ffc8b3e2a4c  (높은 주소)

핵심 정리

Start-up 루틴

커널 → _start → __libc_start_main → main()
ASLR로 인해 주소는 실행마다 바뀜

명령줄 인수

int main(int argc, char *argv[])
argv[0]: 프로그램 이름
argv[1~argc-1]: 실제 인수

환경 변수

extern char **environ;  // 전체 목록
getenv(name)            // 값 가져오기
setenv(name, val, 1)    // 값 설정
unsetenv(name)          // 삭제

자식 프로세스 변경 → 부모에 영향 없음!

메모리 구조

Text (Code) → Read-Only! 수정 시도 시 Segfault
Data        → 초기화된 전역변수
BSS         → 초기화 안 된 전역변수 (0으로 자동 초기화)
Heap        → malloc (위로 성장 ↑)
Stack       → 지역변수 (아래로 성장 ↓)

주의사항

char *s = "literal"; strcpy(s, "new");  // Segfaultchar s[] = "literal"; strcpy(s, "new"); // OKchar *p = malloc(10); p = "leak";       // Memory Leakchar *p = malloc(10); strcpy(p, "ok"); free(p);

참고 자료

  • Linux Memory Layout
  • APUE (Advanced Programming in the UNIX Environment)
  • Linux man pages: man environ, man getenv
profile
임베디드 개발자가 되기 위해 공부중입니다!

0개의 댓글