본문 바로가기

프로그래밍(TA, AA)/C C++

[시스템프로그래밍] 주요 정리

유닉스 시스템 프로그래밍 개요


1. 프로그래밍 표준

유닉스 시스템 프로그래밍 관련 표준으로는 ANSI C, IEEE의 POSIX, X/Open 그룹의 XPG3, XPG4, SVID, SUS가 있습니다.


2. 유닉스 시스템 프로그래밍의 이해

유닉스 시스템이 제공하는 파일 시스템이나 사용자 정보, 시스템 시간 정보, 네트워킹 등 다양한 서비스를 이용해 프로그램을 구현할 수 있도록 제공되는 프로그래밍 인터페이스를 시스템 호출이라고 하며, 이러한 시스템 호출을 사용해 프로그램을 작성하는 일을 유닉스 시스템 프로그래밍이라고 합니다.


3. 시스템 호출과 라이브러리 함수

시스템 호출은 기본적으로 C언어의 함수 형태로 제공됩니다. 시스템 호출은 커널의 해당 모듈을 직접 호출해 작업을 수행하고 결과를 리턴합니다. 라이브러리 함수는 일반적으로 커널 모듈을 직접 호출하지 않습니다. 라이브러리 함수가 커널의 서비스를 이용해야 할 경우에는 시스템 호출을 사용합니다.


4. 유닉스 시스템 프로그래밍 도구

역할

 함수 및 도구

 기본 명령

 pwd, ls, cd, mkdir, cp, mv, rm, ps, kill, tar, vi

 오류 처리 함수

 perror, strerror

 동적 메모리 할당 함수

 malloc, calloc, realloc, free

 명령행 인자 처리 함수

 getopt

 컴파일러

 GNC C 컴파일러(gcc)

 컴파일 도구

 make 명령과 Makefile



- 유닉스 시스템 프로그래밍이 무엇인지 간략하게 설명하라.

- 시스템 호출과 라이브러리 함수의 동작 과정을 비교하여 설명하라.

- mkdir은 명령이기도 하지만, 시스템 호출이기도 하다. 명령과 시스템 호출에 해당하는 맨 페이지를 보는 방법은 각각 무엇인가?

- access 함수를 사용한 프로그램을 작성했는데 오류가 발생했다. errno 변수를 출력해보니 13이 출력되었다. 이 13이 어떤 오류인지 확인하는 방법을 설명하라.

- rmdir 명령으로 temp 디렉토리를 삭제하려고 했는데 디렉토리가 비어 있지 않다는 오류 메시지를 출력했다. 무엇이 문제인가?

- 소스 파일(.c)을 컴파일해 실행 파일을 생성하는 과정을 설명하라.

- Makefile을 사용하는 장점은 무엇인가?

- 프로그램을 실행하는 도중에 정수값 200개를 저장할 수 있는 메모리를 할당하려면 어떻게 해야 하는가?

- 명령행 인자와 getopt 함수를 사용해 다음 명령을 처리하는 프로그램을 작성하라.

1) 명령의 이름: hanopt

2) 명령의 옵션과 동작

인자가 없을 경우: -h와 마찬가지로 사용 가능한 옵션의 목록 출력

-a: "Welcome to Unix System Programming World!!!" 출력

-u 인자: "Nice to meet 인자" 형태로 출력

-h: 사용 가능한 옵션의 목록 출력



파일 입출력


- 고수준 파일 입출력은 '표준 입출력 라이브러리'라고도 부르며, C언어의 표준 함수로 제공된다. 데이터를 바이트 단위가 아니라 버퍼를 이용하여 한꺼번에 읽거나 쓰기를 수행합니다.


- 저수준 파일입출력과 고수준 파일 입출력의 비교

 

 저수준 파일 입출력

 고수준 파일 입출력

 파일 지시자

 int fd (파일 기술자)

 FILE *fp; (파일 포인터)

 특징

 더 빠르다.

 바이트 단위로 읽고 쓴다.

 특수 파일에 대한 접근이 가능하다.

 사용하기 쉽다.

 버퍼 단위로 읽고 쓴다.

 데이터의 입출력 동기화가 쉽다.

 여러 가지 형식을 지원한다.


- 파일 기술자와 파일 포인터 변환

저수준 파일 입출력에서 사용하는 파일 기술자와 고수준 파일 입출력에서 사용하는 파일 포인터를 바꿀 수 있다.


- 임시 파일 생성

프로그램을 수행하는 과정에서 데이터를 파일에 임시로 저장할 필요가 있을 때 임시 파일의 이름이 중복되지 않도록 생성하는 방법이 필요하다.



파일과 디렉토리


1. 유닉스 파일의 특징

- 유닉스에서는 일반 파일뿐만 아니라, 디렉토리, 장치도 파일로 접근한다.

- 파일의 종류: 일반 파일, 특수 파일, 디렉토리

- 파일의 구성 요소: 파일명, inode, 데이터 블록


2. 파일 관련 함수

유닉스에서 파일의 정보는 inode에서 읽어온다. inode 정보의 검색과 파일에 대한 접근권한 확인과 변경을 위해 사용할 수 있는 함수들이 제공된다.


- 파일 정보 검색을 위한 구조체: struct stat(stat.h에 정의됨)

- 파일 접근 권한: stat 구조체의 st_mode 항목에 저장됨

   : 미리 정의된 상수와 매크로를 이용해 st_mode 항목의 값을 해석함


3. 링크 파일 관련 함수

- 링크는 이미 있는 파일이나 디렉토리에 접근할 수 있는 새로운 이름을 뜻한다.

- 하드 링크는 기존의 파일과 같은 inode를 사용한다.- 심볼릭 링크는 기존의 파일에 접근할 수 있는 다른 파일을 만든다. 기존 파일과 다른 inode를 사용하며, 기존 파일의 경로를 저장하고 있다.


4. 디렉토리 관련 함수

유닉스에서는 디렉토리도 파일로 취급하며, 함수를 사용하여 디렉토리를 생성하고 삭제하고, 디렉토리를 이동하거나 디렉토리의 내용을 읽을 수 있다.



시스템 정보


1. 시스템 관련 정보

- 시스템에 설치된 운영체제에 대한 정보, 호스트 이름에 대한 정보, 하드웨어 종류에 대한 정보들을 검색할 수 있다.

- 최대 프로세스 개수나 프로세스 당 열 수 있는 최대 파일 개수, 메모리 페이지 크기 등 하드웨어에 따라 사용할 수 있는 자원들의 최댓값을 검색하거나 수정할 수 있다.

- 시스템 관련 정보 검색


2. 사용자 정보

- 유닉스 시스템에는 각 사용자에 대한 정보, 그룹에 대한 정보, 로그인 기록 정보가 있다.

- 관련 파일: 패스워드 파일(/etc/passwd)과 섀도우 파일(/etc/shadow), 그룹 파일(/etc/group), 로그인 기록 파일(/var/adm/utmpx)


3. 시간 관리 함수

1970년 1월 1일 0시 0분 0초부터 현재까지 경과된 시간을 초 단위로 저장하고, 이를 기준으로 시간 정보를 관리하며, 시간 정보를 관리하는 다양한 함수를 제공한다.



프로세스 정보


1. 프로세스의 개념

- 프로세스는 현재 실행 중인 프로그램이다.

- 프로세스 목록을 보려면 pas, prstat, sdtprocess, top 명령을 사용한다.

- 프로세스는 텍스트 영역, 데이터 영역, 힙, 스택, 빈 공간으로 구성된다.


2. 프로세스 식별 함수


3. 프로세스 실행 시간 측정

- 프로세스 실행 시간 = 시스템 실행 시간 + 사용자 시ㅏㄹ행 시간

- 프로세스 실행 시간 측정 함수: clock_t times(struct tms *buffer);


4. 환경 변수 활용

- 프로세스가 실행되는 기본 환경은 사용자의 로그인명, 로그인 쉘, 경로명 등을 포함하며, 환경변수로 정의되어 있다.

- 환경 변수는 '환경변수 = 값' 형태로 구성되며, 환경 변수명은 관례적으로 대문자를 사용한다. 환경 변수는 쉘에서 값을 설정하거나 변경할 수 있으며, 함수를 이용해 읽거나 설정할 수도 있다.

- 전역 변수 사용: extern char **environ;

- main 함수의 인자: int main(int argc, char **argv, char **envp) {...}



프로세스의 생성과 실행


1. 프로세스 생성

- 함수를 호출하여 프로그램 내에서 다른 프로그램을 실행시켜 프로세스를 생성할 수 있다.

- system 함수는 새로 실행시킬 명령을 인자로 지정하여 호출한다.

- fork 함수는 유닉스에서 프로세스를 생성하는 대표적인 방법으로 부모 프로세스를 복사하여 자식 프로세스를 생성한다.


2. 프로세스 종료

- 유닉스는 프로세스가 종료되면 프로세스가 어떻게 종료되었는지를 나타내는 종료 상태(status)를 저장한다.

- 자식 프로세스는 부모 프로세스에게 자신이 어떻게 종료되었는지를 알려주는 종료 상태 값을 리턴할 수도 있다.

- 일반적으로 종료 상태값이 0이면 정상적으로 종료되었음을 의미하며, 함수 종료 상태값이 0이 아니라면 오류가 발생했음을 뜻한다.


3. exec 함수군

- exec 함수군은 이 함수를 호출한 프로세스의 메모리에 인자로 지정한 다른 프로그램을 덮어써 새로운 프로그램을 실행시킨다.

- exec 함수군은 fork 함수와 연결하여 fork로 생성한 자식 프로세스가 새로운 프로그램을 실행하도록 할 때 유용하게 사용된다.


4. 프로세스 동기화

- 부모 프로세스와 자식 프로세스 사이의 종료 절차가 제대로 진행되지 않을 경우, 이때 좀비 프로세스 같은 불안정한 상태의 프로세스가 발생하는데, 이를 막으려면 부모 프로세스와 자식 프로세스를 동기화해야 한다.



시그널


1. 시그널의 개념

시그널은 소프트웨어 인터럽트로, 프로세스에 뭔가 발생했음을 알리는 간단한 메시지를 비동기적으로 보내는 것이다.


2. 시그널의 처리 방법

 - 프로세스가 받은 시그널에 따라 프로세스를 종료한다.

 - 프로세스가 받은 시그널을 무시한다.

 - 프로세스는 시그널을 처리하도록 함수를 미리 지정해놓는다.

 - 프로세스는 특정 부분이 실행되는 동안 시그널이 발생하지 않도록 블록한다.


3. 시그널 핸들러

프로세스를 종료하기 전에 처리할 작업이 남아 있거나, 특정 시그널에 대해서는 종료하고 싶지 않을 경우 수행할 함수를 지정할 수 있다. 이런 함수를 시그널 핸들러라고 한다.


4. 시그널 집합

POSIX에서 복수의 시그널을 처리할 목적으로 도입한 개념을 시그널 집합이라고 한다. 시그널 집합을 사용하면 여러 시그널을 지정해 처리할 수 있다.


5. 알람 시그널

알람 시그널은 일정한 시간이 지난 후에 자동으로 시그널이 발생하도록 한다. 일정한 시간 후에 한 번 발생시킬 수도 있고, 일정 시간 간격을 두고 주기적으로 알람 시그널을 발생시킬 수도 있다.


6. 시그널 관련 함수

시그널 보내기 / 시그널 핸들러 지정 / 시그널 집합 / 시그널 제어 / 알람 시그널 / 인터벌 타이머 / 시그널 정보 출력 / 시그널 블록과 해제 / 시그널 기다리기



메모리 매핑


1. 통신 프로그래밍의 개념

두 개 이상의 프로세스 사이에서 시그널이나 데이터를 주고받으며 작업을 처리하려면 통신 기능을 이용해 프로그래밍해야 한다. 유닉스 시스템이 제공하는 통신 기능은 크게 두 가지로 구분할 수 있다.

- 프로세스 간 통신: 동일한 시스템 안에서 프로세스 사이에서 통신을 수행한다.

- 네트워크 통신: 서로 다른 시스템에서 수행하고 있는 프로세스 사이에서 통신을 수행한다.


2. 프로세스 간 통신

프로세스 간 통신은 동일한 유닉스 시스템 안에서 수행 중인 프로세스끼리 데이터를 주고받는 것이다. IPC에는 메모리 매핑, 파이프, 시스템 V에서 제공하는 메시지 큐, 공유 메모리, 세마포어 등의 방법이 있다.


3. 네트워크를 이용한 통신

유닉스 시스템에서 서로 다른 시스템 간 네트워크를 이용한 통신은 TCP/IP 프로토콜을 기본으로 하고 있다. TCP/IP 프로토콜을 이용한 프로그램을 작성할 수 있도록 제공되는 인터페이스에는 소켓 인터페이스와 TLI가 있으나 주로 소켓 인터페이스를 사용한다.


4. 메모리 매핑관련 함수

메모리 매핑 기능은 파일을 프로세스의 가상 주소 공간으로 매핑하는 것이다. 따라서 read나 write 함수를 사용하지 않고도 프로그램에서 데이터를 읽거나 쓸 수 있다. 메모리 매핑 함수를 사용하면 매핑된 파일을 부모 프로세스와 자식 프로세스에서 공유하도록 만들고 이를 이용해 토신할 수 있다.



파이프


1. 파이프

- 파이프는 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원하는 특수 파일을 의미한다.

- 파이프는 이름 없는 파이프(익명 파이프)와 이름 있는 파이프로 구분된다.


2. 이름 없는 파이프

- 이름 없는 파이프는 부모-자식 프로세스 간에 통신할 수 있도록 한다.

- 파이프는 기본적으로 단방향으로 통신을 수행한다.

- 파이프를 이용해 양방향 통신을 하려면 파이프 두 개를 만들어야 한다.


3. 이름 있는 파이프

- 부모-자식 프로세스 관계가 아닌 동립적인 프로세스들은 서로의 존재를 알 수 없기 때문에 파이프를 이요하려면 파이프명이 있어야 한다. 이런 기능을 제공해 주는 파이프를 이름 있는 파이프라고 한다.

- 이름 있는 파이프는 특수 파일의 한 종류로, FIFO(First-In First-Out)라고도 한다.

- FIFO는 명령으로 생성할 수도 있고, 프로그램 안에서 함수로 생성할 수도 있다.



시스템 V의 프로세스 간 통신


1. 시스템 V IPC

- 시스템 V 계열 유닉스에서 개발해 제공하는 프로세스 간 통신 방법이 있는데, 이를 시스템 V IPC라고 한다.

- 시스템 V IPC에는 메시지 큐, 공유 메모리, 세마포어가 있다.

- 시스템 V IPC를 사용하려면 IPC의 객체를 생성해야 하며, 키를 지정해야 한다. 현재 사용 중인 각 IPC의 상태를 확인하고, 사용을 마친 객체는 삭제할 수 있도록 관리 명령을 제공한다.



2. 메시지 큐

- 메시지 큐는 메시지(또는 패킷) 단위로 동작한다. 각 메시지의 최대 크기는 제한되어 있다. 각 메시지는 메시지 유형이 있다. 수신 프로세스는 어떤 유형의 메시지를 받을 것인지 선택할 수 있다.


 기능

 함수원형

 메시지 큐 생성

 int msgget(key_t key, int msgflg);

 메시지 전송

 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

 메시지 수신

 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long int msgtyp, int msgflg);

 메시지 제어

 int msgctl(int msqid, int cmd, struct msqid_ds *buf);



3. 공유 메모리

- 공유 메모리는 같은 메모리 공간을 두 개 이상의 프로세스가 공유해 데이터를 주고받을 수 있게 한다.


 기능

 함수원형

 공유 메모리 생성

 int shmget(key_t key, size_t size, int shmflg);

 공유 메모리 연결

 void *shmat(int shmid, const void *shmaddr, int shmflg);

 공유 메모리 해제

 int shmdt(char *shmaddr);

 공유 메모리 제어

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);



4. 세마포어

- 세마포어는 프로세스 사이의 동기를 맞춘다.

- 세마포어는 한 번에 한 프로세스만 작업을 수행하는 부분에 접근해 잠그거나, 다시 잠금을 해제하는 기능을 제공하는 정수형 변수다.


 기능

 함수원형

 세마포어 생성

 int semget(key_t key, int nsems, int semflg);

 세마포어 제어

 int semctl(int semid, int semnum, int cmd, ...);

 세마포어 연산

 int semop(int semid, struct sembuf *sops, size_t nsops);




소켓 프로그래밍 기초


1. TCP/IP 프로토콜 개요

TCP/IP는 네트워크를 통해 호스트 사이에서 통신할 수 있도록 하며, 5개의 계층으로 이루어져 있다.

- 응용 계층, 전송 계층, 네트워크 계층, 네트워크 접속 계층, 하드웨어 계층

- 이중 하드웨어 계층과 네트워크 접속 계층을 묶어서 4개의 계층으로 구분하기도 한다.

- TCP/IP 중 전송 계층에서는 TCP와 UDP 프로토콜을 제공한다. 두 프로토콜의 차이점을 요약하면 다음과 같다.


 TCP

 UDP

 연결지향형(connection-oriented)

 비연결형(connectionless)

 신뢰성(reliability) 보장

 신뢰성을 보장하지 않음

 흐름 제어 기능(flow-control) 제공

 흐름 제어 기능 없음

 순서 보장(sequenced)

 순서를 보장하지 않음(no sequence)


2. TCP/IP 프로그래밍을 위한 준비 사항

IP 주소와 호스트명

- IP 주소: 인터넷을 이용할 때 사용하는 주소로, 점(.)으로 구분된 구분된 32비트 숫자로 표시한다.

- 호스트명: 사람이 시스템을 구분하기 위해 붙이는 이름으로 '호스트명 + 도메인명' 형태로 구성된다.

- 호스트명과 IP 주소 관련 파일: /etc/nsswitch.conf, /etc/hosts


포트 번호

- 호스트를 구별하는 IP 주소 외에 서비스를 구분하는 번호로 2바이트 정수고, 0~655535까지 사용할 수 있다.

- 인터넷에서 자주 사용하는 서비스는 이미 포트 번호가 지정되어 있다. 이를 잘 알려진 포트라고 하며, 0~1023까지 사용한다.

- 포트 번호 등록 파일: /etc/services


바이트 순서: 정수를 저장하는 순서

- 네트워크 바이트 순서(NBO: Network Byte Order), 호스트 바이트 순서(HBO: Host Byte Order)


3. 소켓 인터페이스

TCP/IP 프로토콜을 이용한 응용 프로그램 작성을 편리하게 해주는 인터페이스다.

소켓 인터페이스를 이용하려면 소켓의 종류와 통신 방식을 지정해야 한다.

- 소켓 종류: AF_UNIX(유닉스 도메인 소켓), AF_INET(인터넷 소켓)

- 소켓 통신 방식: SOCK_STREAM(TCP 프로토콜 사용), SOCK_DGRAM(UDP 프로토콜 사용)

- 소켓 인터페이스 함수

소켓 생성: int socket(int domain, int type, int protocol);

소켓을 IP 주소, 포트 번호와 결합: int bind(int s, const struct sockaddr *name, int namelen);

클라이언트와 통신 준비 완료: int listen(int s, int backlog);

클라이언트가 서버에 접속 요청: int connect(int s, const struct sockaddr *name, int namelen);

클라이언트의 접속 대기 및 허용: int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

데이터 수신(SOCK_STREAM): sszie_t recv

데이터 송신(SOCK_STREAM): ssize_t send

데이터 수신(SOCK_DGRAM): ssize_t recvfrom

데이터 송신(SOCK_DGRAM): ssize_t sendto



소켓 프로그래밍 활용


1. 서버 프로세스의 동작 형태

- 반복 서버(iterative server): 데몬 프로세스가 직접 모든 클라이언트의 요청을 차례로 처리한다.

- 동시 동작 서버(concurrent server): 데몬 프로세스가 직접 서비스를 제공하지 않고, 서비스와 관련있는 다른 프로세스를 fork 함수로 생성해 클라이언트와 연결한다.


2. TCP/UDP 기반 프로그래밍의 차이점

 항목

 TCP 기반 프로그래밍

 UDP 기반 프로그래밍

 통신 방식

 SOCK_STREAM으로 지정

 SOCK_DGRAM으로 지정

 서버

 bind, listen, accept 함수를 차례로 사용

 bind 함수를 수행한 후 listen 함수를 사용하지 않음

 클라이언트

 - connect 함수를 사용해 명시적으로 서버 지정

 - 서버와 연결을 확보한 후 recv 하수나 send 함수를 통해 데이터를 주고받음

 - connect 함수를 사용하지 않음

 - 클라이언트의 연결 요청이 별도로 없으므로 서버가 클라이언트로 데이터를 보낼 때 클라이언트의 주소를 구조체로 지정해야 함.



3. TCP와 UDP 기반 프로그래밍에서 주의할 점

- 먼저 서버와 클라이언트가 주고받을 데이터가 어떤 형태인지, 얼마나 중요한 것인지를 결정한다. 이에 따라 TCP를 사용할 것인지, UDP를 사용할 것인지 결정할 수 있다.

- TCP는 신뢰성이 중요한 응용 프로그램에 사용하는 것이 바람직하며, UDP는 신뢰성보다는 빠르게 전달하고 응답을 받는 것이 중요한 응용 프로그램에 사용하는 것이 좋다.
- 서버 프로세스를 반복 서버 형태로 구현해도 클라이언트의 요청을 충분히 처리할 수 있는지, 아니면 동시 동작 서버 형태로 구현하는 것이 필요한지 신중하게 판단해야 한다.