-
[시스템프로그래밍] UNIX System Overview개발 이야기/운영체제(OS)&시스템프로그래밍 2024. 10. 20. 22:35
본 내용은 한양대학교 조인휘 교수님의 시스템 프로그래밍 강의 자료를 기반으로 작성되었습니다
왜 운영체제와 시스템 프로그래밍을 공부해야하는가?
시스템 프로그래밍은 컴퓨터 하드웨어가 사용자와 상호작용하여 컴퓨터 시스템에서 소프트웨어를 효과적으로 실행하기 위해 공부하는 내용이다. 운영체제와 같은 kernel 및 핵심 library를 직접 사용하여 Low-level에서 동작하는 시스템 프로그램을 작성하기 위한 기술이다.
수업의 내용에 따라 아래의 내용을 중심으로 정리하였다.
1. Unix System Overview / Unix Standardization and Implementations
2. File에 관한 내용: File I/O, Files and Directories, Standard I/O Library
3. Process에 관한 내용: Process Environment, Process Control, Process Relationships
4. I/O에 관한 내용: Advanced I/O, Network sockets
시스템 콜과 운영체제
좋은 비유가 있어서 가져왔다(https://velog.io/@eddy_song/system-call)
운영체제는 호텔의 프론트 데스크와 같다
컴퓨터를 쓴다는 것은 동시에 여러 프로그램이 다양한 하드웨어와 컴퓨터 리소스를 사용하며 각자의 명령어를 실행하는 과정이다.
즉, 하드웨어를 효율적으로 사용하고, 안전하게 사용하기 위해 이와 관련된 서비스를 제공한는 것이 결국 시스템 프로그램이 하는 일이고, 운영체제가 존재하는 목적이다.
위의 비유를 이용하면, 컴퓨터의 하드웨어 및 리소스는 호텔의 방과 각종 시설과 같다. 이들은 대부분 한번에 하나씩만 이용가능하다.
손님들은 실행중인 프로그램 혹은 기다리고 있는 프로그램이다. 이들은 호텔의 방에 배정받기를 기다린다(말을 대부분 잘 들어주는 착한 손님들이라고 하자)
마지막으로 OS는 손님들의 요구에 맞추어 적절히 방을 배정하고 이들을 안내한다.
손님들의 요구는 바로 System call에 의해서 전달된다.
그럼 시스템 콜이란 무엇일까?
System call
"엄밀히 말해, 시스템 콜이 곧 운영체제다. 시스템 콜이 운영체제의 서비스를 정의하기 때문이다."
<유닉스의 탄생>, 브라이언 커닝핸위에서 예시를 다시 가져와, 만일 모든 손님들이 알아서 호텔을 이용한다고 해보자. 막 손님이 있는 방에 갑자기 벌컥하고 다른 손님이 들어가거나, 혹은 호텔의 돈을 빼가거나, 방명록을 조작할 수 도 있을 것이다. 이는 위에서 말한 효율적인 리소스 분배와 안전한 사용에 위배된다. 이런 상황을 막기 위해 우리는 커널을 시스템 콜을 통해 감싸두었다.
위 그림을 보고 각각을 하나씩 봐보자.
커널: 운영체제의 핵심부분, 컴퓨터의 자원을 관리한다. 하드웨어와 응용 프로그램 사이에서 인터페이스를 제공하는 역할을 한다.
커널이라는 뜻은 알맹이, 핵심을 의미한다고 한다. 하드웨어와 직접적으로 상호작용한다고 생각하면 된다.
System call: 커널과 응용프로그램 간의 인터페이스 역할이다. 모든 사용자, 혹은 프로세는 시스템 자원을 사용하기 위해서는 시스템 콜을 거쳐야하며 이는 커널에 하드웨어 자원을 이용하기 위해 요청을 하는 것과 같다.
Libraray routines:운영체제에서 제공하는 함수나 메소드의 모음, 라이브러리라고 생각하면 되고, 반복적인 코드 작성을 하지 않아도 되게 해준다.
Shell(쉘): 사용자가 운영체제와 상호작용 가능한 인터페이스를 제공, 사용자가 입력한 명령을 실행하기 위해 시스템 콜을 사용하기도 함. 명령 인터페이스(CLI)나 GUI가 대표적인 예시.
History of UNIX
보통 UNIX에 관해 들어본 사람들은 LINUX, 리눅스도 들어본적이 많을 것이다. 간단히 그들에 관해 정리하면 다음과 같다.
(https://velog.io/@eddy_song/os-standard / https://velog.io/@ifyouseeksoomi/CS-Operating-System-Unix-Linux-Ubuntu-macOS-windows 잘 정리된 글이 있어서 첨부)
- 유닉스
현대 운영체제의 시작, 알파. 우리가 쓰는 대부분의 운영체제는 이 유닉스를 뿌리로 한다(윈도우 빼고, 얘는 origin by window계열이다)
유닉스는 1960년대 후반 멀틱스라는 운영체제 개발 프로젝트를 진행했고, 관련 개발은 상업적으로 실패했지만 이름에서 알수 있듯이 멀티 프로그래밍, 멀티 유저를 지원했다.
이후 벨 연구소의 켄 톰슨과 데니스 리치가 멀틱스에서 느낀 문제점을 개선하여 UNIX를 개발하였고 이후 저렴한 가격에 외부 배포를 했다. 이후 유닉스 배포판을 가지고 전세계 개발자들이 수정, 개선하기 시작하였다. 위 그림을 보면 알 수 있지만, 엄청나게 많은 버전이 이후 생겨났다.크게 2가지 계열이 있는데, BSD (Berkeley Software Distribution) 계열로, 이는 버클리 대학에서 만들고 배포한 것이다. 1985년 스티브 잡스가 만든 NeXT 컴퓨터의 운영체제가 BSD 기반이었다. 이후 잡스가 돌아가면서 NeXT는 MacOS의 기반이 됐다
System V는 AT&T가 유닉스의 저작권을 가지고 만든 계열로 시스템 공급업체들이 사용하는 OS의 기반이 되었다.
이후 여러 계열일 생긴탓에 표준화가 필요해졌고, 결과적으로 업계에서는 2가지의 핵심 표준이 유닉스 계열 운영체제의 표준으로 자리잡았는데
하나는 POSIX이고, 다른 하나는 SUS이다.- POSIX와 SUS
POSIX (Portable Operating System Interface)는 IEEE가 정의한 운영체제 인터페이스 표준이다. 더 자세히 말하면, 시스템 콜에 대한 C 언어 API 표준이다.
POSIX는 '유닉스 계열'이라고 불리는 운영체제의 기준이다. BSD, Linux, MacOS 등은 유닉스는 아니지만, OSIX를 따르기 때문에 유닉스 계열이라고 부른다.
SUS(Single Unix Standard)는 유닉스의 상표권을 가진 오픈 그룹이 만든 유닉스 표준이다. SUS는 POSIX를 포함하는 상위 집합이다.
SUS는 '유닉스'라고 불리는 운영체제의 기준이다. SUS와 호환이 되는 운영체제는 ‘유닉스'라고 불릴 수 있다.MS사의 경우, 유닉스 계열은 아니지만 POSIX 표준을 따르는 서브 시스템을 제공하는데, 이것이 WSL이다.
- LINUX
맞다. 그 리눅스 토발즈의 리눅스이다. 이는 유닉스와 호환이 되는 일부 기능을 담은 미닉스를 기반으로 리눅스 토발즈가 21살때 무료 운영체제를 개발, 많은 사람들이 함께 참여한 프로젝트의 결과물이다.리눅스는 POXIS 표준의 일부로 유사한 개념을 공유한다. 또한 WSL(Windows Subsystem for Linux)라는 이름으로 유닉스 기반이 아닌 윈도우에도 리눅스 커널을 설치가능하다.
그 유명한 우분투 또한 리눅스를 기반으로 개발되었다.
FIle: 파일이란 무엇인가
파일은 보조 저장 장치에 정보를 저장하는 단위이다. 운영체제는 정보를 파일 형태로 쓴 뒤 저장하고 찾아서 읽는다.
파일은 저장장치에 효율적이면서도 찾을 수 있도록 저장되어야한다. 즉, 관리되어야하는데 이를 관리하는 체계를 파일 시스템이라고 한다. 파일 시스템은 어떤 운영체제든 필수로 포함되어있지만, 종류는 다 다르다. 도서관마다 책을 관리하는 방법이 다 다르듯이, 저장공간도 각자 쓰는 파일 시스템이 다를 수 있다.
대표적으로 윈도우는 FAT(12/16/32, exFAT), NTFS, 리눅스는 ext(2/3/4), 맥OS는 HFS+, APFS 등의 파일 시스템을 사용한다.유닉스/리눅스 계열 시스템 프로그래밍에서는 거의 모든 자원과 서비스를 파일 형태로 표현한다.
일반적으로 파일은 '실행 프로그램'이나 '데이터'를 저장한다.
하지만 유닉스에선 '저장 장치', '입출력 장치', '네트워크 통신'도 모두 파일이다. 즉, 키보드, 마우스, 디스크, 디스플레이, 인터넷 소켓, 파이프 등등 모두 다 파일이다.
파일의 내용을 읽을 때도 read()로 하고, 사용자가 키보드에서 입력한 글자를 받아올 때도 read()로 한다.이를 통해 인터페이스가 통일되고, 파일을 다루는 툴과 인터페이스로 다양한 시스템 자원을 다룰 수 있다.
예전 운영체제에서는 입출력 장치를 사용할 때 실제 장치의 복잡한 세부사항을 알아야 했다.
하지만 유닉스는 이런 부분을 파일로 최대한 추상화하고, 그냥 파일을 저장하고 읽어오는 인터페이스로 장치를 다룰 수 있게 만들었다(이런 장치 파일은 일반 파일과 다르게 중간에 또다른 소프트웨어가 있다. '디바이스 드라이버'다. 장치를 조작할 수 있는 단순한 인터페이스를 제공한다.)즉 결국 모든 것은 파일이다.
는 매우 핵심적인 유닉스의 철학이다.
파일의 경로 등 inode 등에 관한 내용은 차후 다른 글에서 자세히 살펴보자.
Logging In
login, 위에서 말햇듯이 멀티 유저 시스템이기에 login nam + password로 시스템에 로그인한다.
/etc/passwd라는 파일에 아래와 같은 형식으로 정보를 저장한다. 이는 모든 user가 볼 수 있는 정보이다
위 그림을 참조하자. login name:x:user ID:group ID:comment:home directory:shell 형태로 저장한다.
이때 x: 패스워드 필드는 보안상의 이유로 /etc/shadow 파일에 저장되어 있어 x로 대체한다.
/etc/shadow에서는 사용자 계정의 암호화된 비밀번호와 추가적인 비밀번호 관리 정보를 저장한다. 따라서 root만 접근 가능하다. 아래와 같이 구성된다.
- username: 사용자 이름
- $6$saltsalt$encryptedpassword: 암호화된 비밀번호 (비밀번호 해싱 알고리즘과 솔트 포함)
- 18548: 마지막으로 비밀번호가 변경된 날짜 (1970년 1월 1일을 기준으로 한 일 수)
- 0: 비밀번호 변경 가능한 최소 일 수
- 99999: 비밀번호가 만료되기 전 최대 일 수
- 7: 비밀번호 만료 전 사용자에게 경고할 일 수
- 나머지 필드는 비어 있거나 추가적인 패스워드 관리 옵션 의미
username:$6$saltsalt$encryptedpassword:18548:0:99999:7:::
Input / Output
File Descriptors: 파일 디스크립터는 파일을 식별하는 음수가 아닌 정수이다. 이는 파일을 고유하게 식별하는데 사용하며, 각 파일에 접근하기 위해 파일 디스크립터가 필요하다.
표준 입력, 출력 및 오류 (Standard Input, Output, Error)
- stdin (Standard Input): 표준 입력을 나타내는 파일 디스크립터로, 기본적으로 키보드를 입력으로 사용한다. 파일 디스크립터 번호는 0이다.
- stdout (Standard Output): 표준 출력을 나타내는 파일 디스크립터로, 기본적으로 화면에 출력. 파일 디스크립터 번호는 1이다.
- stderr (Standard Error): 표준 오류 출력을 나타내는 파일 디스크립터로, 오류 메시지를 확면에 출력 . 파일 디스크립터 번호는 2이다.
Unbuffered I/O vs. Standard I/O
Buffer란 완충을 의미한다. 즉, 버퍼를 이용하여 정보를 보다 효율적으로 입력/출력한다.
Unbufffer I/O는 open, read, write 등의 시스템 콜을 사용하여 파일을 직접적으로 처리하며, 버퍼링 없이 즉각적인 파일의 읽기 쓰기를 관리한다(User가 버퍼를 지정)
Standard I/O는 f가 붙은 표준 입출력을 의미하고 fopen, fgetc, printf 등의 함수로 구현되며, 라이브러리를 통해 간접적으로 파일의 읽기 쓰기를 지원한다. 또한 그 과정에서 buffer를 automatically 사용한다.
Programs & Process
Program: 디스크에 저장된 실행파일을 의미한다. 프로그램은 파일 시스템에 존재하는 정적인 상태의 파일로 실행 전까지는 시스템 자원을 사용하지 않는다.
Process는 실행중인 프로그램의 인스턴스로, 프로세스는 프로그램이 메모리에 적재되고 CPU에서 실행되는 동적인 상태를 의미한다. 즉, 프로세스는 운영체제의 관리 대상이며, 운영체제에 의해 할당된 리소스를 사용한다.
이런 프로세스는 PID, process id로 식별되며 이 또한 고유한 비음수 정수로 구성된다. 운영체제는 PID를 통해 프로세스를 관리한다.
프로세스 제어 (Process Control)
- fork(): 새로운 프로세스를 생성한다. 새로 생성된 프로세스는 부모 프로세스의 복사본으로 실행되며, 부모와 자식 프로세스는 서로 다른 PID를 가지게 된다. 이는 동시간 대에 다른 일을 각 프로세스가 할 수 있게 해준다.
- 부모 프로세스는 fork()의 반환 값이 자식 프로세스의 PID가 되며, 자식 프로세스는 0을 반환받는다.
- 즉, fork()가 성공적으로 호풀되면 부모 프로세스는 자식 프로세스의 PID를 반환받아 이를 관리할 수 있다.
- 자식 프로세스는 자신의 PID를 알 필요가 없기에 0을 반환받으며 이는 실행중임을 나타낸다.
- exec(): exec() 함수는 새로운 프로그램을 실행시킨다. 즉, 명령어를 실행한다.
- waitpid(): 부모 프로세스는 자식 프로세스가 종료될 때까지 대기한다, 이는 위에서 말한것처럼 부모 프로세스가 자식 프로세스가 종료될 때까지 관리해야하기 때문이다. 이 시스템 호출은 자식 프로세스가 정상적으로 종료되는지 확인하는 데 사용된다, 즉 Zombie process를 예방한다.
'개발 이야기 > 운영체제(OS)&시스템프로그래밍' 카테고리의 다른 글
[시스템프로그래밍] Standard I/O Library (0) 2024.10.21 [시스템 프로그래밍] Files and Directories / 권한에 관해 (2) 2024.10.21 [시스템 프로그래밍] File I/O (0) 2024.10.21