Linux Programmer

리눅스 시스템 프로그래밍에서 꼭 알아야 할 사전지식 본문

저술 관련/리눅스 시스템 네트워크

리눅스 시스템 프로그래밍에서 꼭 알아야 할 사전지식

sunyzero 2017. 9. 13. 18:22

* 리눅스 시스템 프로그래밍을 배울때 꼭 알아야 할 사전 지식, 혹은 체크 항목


리눅스 시스템 프로그래밍을 배울때는 다음과 같은 항목을 미리 체크해보고, 방향과 교재, 항목을 체크해봐야 한다.

만일 방향을 잘못 잡으면 처음부터 잘못된 길을 갈 수 있으니 공부를 시작하기 전에 최소한 이 정도는 체크해보는 것이 좋다.


1. 어떤 국제 표준에 맞춰서 배울것인가?

리눅스(Linux)는 기본적으로 UNIX standard을 준수한다. 따라서 UNIX 표준과의 호환성을 염두에 둔다면 어떤 표준 레벨에 맞춰서 배울 것인지를 결정해야 한다. 유닉스 표준은 다음과 같은 것들이 있다.[1]


System V (SysV)

Svr4

Svr42

43BSD (4.3 BSD)

POSIX (POSIX.1b, POSIX.1c POSIX.1j ... 버전이 많다.)

SUS (Single UNIX Spec) : SUSv1, SUSv2, SUSv3, SUSv4의 4가지 버전이 있다.

== SUSv4는 IEEE std 1003.1-2008 혹은 POSIX 1003.1-2008으로 표기되기도 한다.


여기에 리눅스/GNU의 비표준 기능도 배울 것인지를 생각해야 한다. 주로 많이 쓰이는 비표준 기능으로는 epoll, mmap extensions, eventfd, signalfd, timerfd, arbitrary kernel buffer, RTS, file cookie 등등이 있다. 컴파일러 비표준, GCC 비표준 문법을 배워야 하는 경우에는 추가로 몇가지를 더 배우기도 한다.


위의 표준 중에 현재 가장 많이 쓰이고, 표준으로서 정립된 부분은 SUSv2 (1998년도)이후의 버전이다. 현재 대부분의 교재에서는 SUSv3, 혹은 SUSv4의 기능을 기준으로 학습하도록 권고하고 있다. SUSv3 (2002년)이후에는 C99의 restricted pointer를 사용하기 때문에 restricted pointer에 대한 기법과 이에 맞춰서 프로그래밍을 해야 한다.


간혹 대학이나 회사에서 유닉스, 혹은 리눅스 시스템 프로그래밍을 배우면서 Svr4 (1988년)의 옛날 기능으로 학습하는 경우가 있는데, 이는 너무 오래되었기 때문에 권장하지 않는다. 이제는 SVR4기반의 시스템 프로그래밍은 구식이 되었다. 기본적으로 6~70% 정도는 비슷하지만 변경된 부분의 차이가 크다.


SUSv4에서 변경되어 과거 버전과 다른 점은 이 링크에서 볼 수 있다.[2]


1.1 최신 국제표준을 배우는 이유는 안전한 프로그래밍을 위해.

최신 표준을 배워야 하는 이유는 보안과 성능 때문이다. 보안적인 측면에서, 혹은 성능 때문에 새로운 함수가 옛날 구식 함수의 기능을 대체하는 경우가 있는데, 호환성 때문에 옛날 구식 함수도 그대로 제공되는게 문제다. 그러다보니 구식 표준으로 배우면 보안에 취약하거나 성능이 안좋은 코드를 만들 수 있는 것이다. 차라리 컴파일이 안되면 그나마 다행인데, 문제가 있는 코드가 얼핏 보기엔 돌아가니까 더 큰 문제를 낳는다.


예를 들어 signal이라는 함수를 생각해보자. 이 함수는 신뢰성에 문제가 있어서 새로운 함수인 sigaction이 나왔다. 그래서 표준안을 보면 signal 함수는 되도록이면 쓰지 말고 sigaction을 쓰라고 쓰여있다. 하지만 아직도 많은 학생들이 구식의 signal 함수로 배우는 경우가 많다.


시간을 재던 gettimeofday도 이젠 구식 함수가 되어 clock_gettime으로 대체되었다. 참고 링크 - https://sunyzero.tistory.com/161


sleep같은 함수도 이제 nanosleep으로 대체되었다. 이외에 mktemp 함수는 보안 때문에 mkstemp가 제공된다.

구식 함수인 valloc도 이젠 쓰이지 않는다. 대신 posix_memalign과 aligned_alloc이 제공된다.


심지어 memcpy는 옛날 함수의 프로토타입이 변경되었다. 현재 새로운 memcpy는 restricted pointer가 적용되어있다. 따라서 memcpy와 memmove를 쓰임새에 따라서 골라서 써야 한다.


이 외에도 세보지는 않았지만 적어도 백여개 이상의 함수들이 새롭게 디자인되고 변경되었다. 특히 2000년 들어서 꽤 많이 변했기 때문에 과거의 시스템 프로그래밍 함수와는 다른 경우가 꽤 많다.


더 큰 문제는 구식 함수도 호환성 때문에 계속 제공되므로 학습할 때 딱히 문제가 없어보인다는 점이다. 하지만 자신도 모르는 사이에 보안이나 성능에 문제가 있는 코드를 생산하기 때문에, 새로운 프로그래밍 표준을 보고 학습하는 것은 매우 중요하다. 그러므로 적어도 시스템 프로그래밍을 하기 전에 http://www.opengroup.org에서 UNIX standard (IEEE std 1003.1)을 다운받아서 살펴보도록 하자. 

(리눅스 man page 영문판도 IEEE std 1003.1의 최신 버전의 내용을 보여주긴 하지만, 간혹 버전 낮은 리눅스에서는 man page가 옛날 표준을 보여주는 경우도 있으니 조심하자. 특히 한글 man page는 99퍼 옛날 버전이므로 보지 말자.)


만일 현재 시스템 프로그래밍을 공부하면서 위의 함수들이나 구식 함수인 bzero, bcopy 뭐 이런 것들이 남발되고 있다면, 즉시 멈추고 UNIX 표준에 대해 다시 한번 조사해보자. 표준에 대한 정확한 이해 없이 공부하면 헛공부가 될 수 있다.



2. 네트워크 프로그래밍을 포함할 것인가?

원래 network programming은 시스템 프로그래밍의 부분 집합이다. 하지만 워낙 덩치가 크기 때문에 네트워크 프로그래밍이라는 분야로 따로 설명되기도 한다.


네트워크 프로그래밍 대신에 소켓 프로그래밍 혹은 TCP/IP 프로그래밍이라고 부르기도 하는데, 엄밀하게는 다르지만, 유닉스/리눅스 분야에서는 그들은 같은 의미로 쓰인다. 따라서 리눅스 네트워크 프로그래밍, 리눅스 소켓 프로그래밍, 리눅스 TCP/IP 프로그래밍은 거의 같은 의미로 쓰인다.


네트워크 프로그래밍을 배울 때에도 버전별 특징이 다르다. 예를 들어 SUSv3이후로는 IPv6 지원이 강화되면서 옛날에 쓰이던 소켓 프로그래밍 기법인 gethostbyname같은 구식 함수는 표준에서 제거되고, 대신에 새로운 함수인 getaddrinfo를 사용하도록 하고 있다. 따라서 네트워크 프로그래밍을 배울 것이라면 새로운 함수체계에 익숙해져야 한다. (심지어 Apple의 iOS는 기본적으로 구버전의 gethostbyname 같은 기능은 지원하지도 않는다. 그래서 애플 마켓에서는 구버전 API를 사용하면 등록조차 거부된다.)


그리고 epoll 같은 비표준 기능은 리눅스에서 네트워크 프로그래밍을 하려면 꼭 필요하므로 배워둬야 한다. 이외에 성능을 위해 sendfile, splice같은 기능들도 같이 배우는 경우가 많다.



3. 병렬 프로그래밍을 포함할 것인가?

위의 네트워크 프로그래밍과 비슷하게 병렬 프로그래밍도 시스템 프로그래밍의 일부지만, 덩치가 크기 때문에 따로 떼어내서 배우는 경우가 많다다.


현재 리눅스에서 C기반으로 사용하는 병렬 프로그래밍은 POSIX thread 즉 pthread (POSIX 1003.1c-1995)라고 불리는 기능을 의미한다. pthread는 네이티브 스레드 프로그래밍에 준하는 유닉스 계열 표준 스레드 프로그래밍 API이다. 사용하는 방법은 조금 복잡하다. 따라서 C11에 들어와서 pthread의 랩핑형태의 스레드 표준 함수가 제공되지만, 아직까지는 pthread를 더 많이 쓰고 있다.


이 외에 OpenMP도 있다. OpenMP는 C, C++, Fortran을 지원한다.

그리고 C++을 대상으로 하는 경우는 전통적인 C API 시스템 프로그래밍과는 조금 다르다. (이건 여기서 논할 내용은 아니므로 스킵.)


4. IPC는 어떤 표준을 배울 것인가?

IPC(Inter-Process Communication)는 프로세스간의 데이터 통신에서 굉장히 중요하게 쓰이는 기법이다. 유닉스 계열에서는 좁은 의미로 세가지 기법이 제공되는데, 세마포어(semaphore), 공유메모리(shared memory), 메세지큐(message queue)가 바로 그것이다.


하지만 IPC의 경우에는 2가지 구현 기법이 SUS표준에 속하는데, 옛날에 SysV 유닉스로부터 유래한 기능을 XSI IPC라고 부르며, POSIX 표준이 만들어지면서 새롭게 디자인된 IPC를 POSIX IPC라고 부른다. 둘다 표준이므로 두가지 기능을 다 배워야 한다. 그래서 XSI 세마포어, POSIX 세마포어, XSI 공유메모리, POSIX 공유메모리, XSI 메세지큐, POSIX 메세지큐 등이 있다. 즉 공유메모리라고 해도 XSI공유메모리인지, 아니면 POSIX공유메모리인지에 따라 다르다.


참고로 옛날 방식인 XSI IPC는 예전에는 주로 사용되다가 2000년대 후반부터는 점점 POSIX IPC를 사용하는 방식으로 바뀌고 있다. 하지만 여전히 둘다 쓰인다. 이에 대한 조금 자세한 글은 아래 링크로 대신한다.


리눅스에서 XSI와 POSIX 메세지큐의 비교 : http://sunyzero.tistory.com/168


이 외에 비표준이지만 리눅스 전용의 IPC기능인 timer fd, signal fd, event fd, inotify같은 기능도 있다.


=======


요약하자면, 프로그래밍 표준안을 어디에 맞출 것인지? 비표준을 어디까지 수용할 것인지? 네트워크나 병렬 프로그래밍도 포함해서 배울 것인지를 결정하는 것이다. 이 정도를 미리 조사해보고 공부해야 실수하지 않는다.


[1] UNIX standards, http://www.opengroup.org

[2] IEEE std 1003.1-2008,2016 Rationale sections http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap03.html



히스토리

* 2017-09-13 첫버전

9 Comments
  • 프로필사진 알 수 없는 사용자 2020.06.14 17:30 다시금 sunyzero님의 글을 보고 있습니다. 처음 이 블로그를 접했을 때에는 잘 몰랐지만, 아는게 많아질수록 더 대단하신 분이라는 걸 여실히 깨닫고 있습니다. 양질의 글 감사합니다. 오늘도 잘 보고 갑니다 : )
  • 프로필사진 sunyzero 2020.06.15 21:24 신고 감사합니다. ^^ 좋은 하루 되시기 바랍니다.
  • 프로필사진 익명 2020.06.22 11:01 비밀댓글입니다
  • 프로필사진 sunyzero 2020.06.22 15:32 신고 안녕하세요.

    독자분이 어떤 수준인지 제가 파악할 수는 없지만 일반적인 경우를 감안해서 말씀드리도록 하겠습니다.

    우선 리눅스에 익숙해지시려면 사용하는 PC를 리눅스로 변경하셔야 합니다. 가상머신으로 잠깐잠깐 쓰면 절대로 익숙해지지 않습니다. 메인 운영체제를 리눅스로 변경하는게 가장 중요합니다.

    그 다음에 C언어 기초가 부족하다면 대부분 KNK를 보는게 좋습니다.

    그 다음에 리눅스 기초 개발책으로는 "모두를 위한 리눅스 프로그래밍"이라는 책을 보는 것이 좋습니다. 내용이 약간 수박 겉햟기라는 면이 있지만 기초가 부족한 분들에게는 좋은 책이기도 합니다.

    라즈베리파이를 이용해서 리눅스 프로그래밍을 배우는 방법도 좋은데 "사물인터넷을 위한 리눅스 프로그래밍 with 라즈베리파이(서영진)" 책이 괜찮습니다. 세부적으로 약간의 내용 오류가 있고 중언부언하는 면이 있긴 하지만 전체적으로 보면 좋은 기초 책입니다.

    이 외에 gdb나 makefile에 대한 것도 배워야 하는데 이건 책이 괜찮은게 없습니다. 따라서 아쉽지만 옛날 자료인 kldp것으로 배우시는 것을 추천드립니다.

    개인적 상담 댓글은 일정 시간후에 삭제하니 양해부탁드립니다.
  • 프로필사진 Iam Andy 2020.06.22 17:27 시간 내서 좋은 조언과 도서 추천을 해주셔서 감사합니다. gdb,make, vim, valgrind 같은 유틸은 최대한 시간날때마다 익히려고 노력하고 있습니다. 현재 원하는 목표는 고급 POSIX 프로그래밍이 가능한 레벨까지 수준을 끌어올려 취직하는 것입니다.
  • 프로필사진 sunyzero 2020.06.23 14:28 신고 이루고자 하시는 모든 일이 잘 풀리기를 바랍니다. ^^
  • 프로필사진 익명 2022.07.09 00:52 안녕하세요. 좋은 책 잘 보고 있습니다.
    책 보던 중에 한 가지 의문점이 생겨 질문드립니다.
    p202에서 (코드 5.26 이후)
    앞서 posix_named_sem_cnt 를 실행하면 교착상태에 빠지기 때문에 교착상태를 해결하는 프로그램이 필요하진다. 이 프로그램에는 명명된 세마포어의 이름을 입력하면 이를 제거하거나 sem_post로 세마포어 값을 증가시키는 기능이 있어야 한다.
    라고 되어있는데, 제거하거나 sem_post로 값을 증가시키는 기능이 있어야 하는 점은 이해가 가지만, 이 경우에는 카운터가 4인 세마포어 한 종류밖에 없기 때문에 교착상태가 일어날 일은 없지 않나요? (점유대기 조건이 만족되지 않기 때문에)
    그리고 그 다음 문단에서 코드 5.24의 55행을 제거하여 확인해 보라고 하시는데 5.26이어야 할 것 같습니다!
    감사합니다.
  • 프로필사진 sunyzero 2022.07.09 16:32 신고 안녕하세요.

    말씀하신대로 오타가 맞습니다. 5.24의 55행이 아니라 5.26의 55행이 맞습니다. 정오표에 등록해두도록 하겠습니다. 감사합니다.

    그리고 코드 5.26의 초기값이 4인 카운팅 세마포어 예제는 최대한 단순하게 만들었기 때문에 그렇습니다. 이 코드는 자식 프로세스가 작업을 완료하면 post 신호를 세마포어에 주도록 되어있습니다. 이런 방식에서 자식 프로세스가 abort하게되면 부모 프로세스는 교착상태에 빠지게 됩니다. 세마포어 본래 목적인 자식 프로세스(or 스레드)의 작업 완료를 받는 배리어 역할을 할수 없게 되는거죠.

    이와 비슷한 형태로 작동하는 것이 그림 5.11에서 첫번째 세마포어입니다. 이는 2의 초기값을 가지는데, Job #a 작업이 2개로 나뉘기 때문이며, 만일 작업이 4개로 나뉜다면 코드 5.26과 비슷한 기능을 하는 것과 같습니다.
  • 프로필사진 익명 2022.07.09 17:06 답변 감사합니다!
댓글쓰기 폼