본문 바로가기
Back-end

2023.08.06 TIL

by 신재권 2023. 8. 7.

Java

interface

인터페이스는 메서드의 시그니처만 구현해놓고, 상세한 동작 방식은 인터페이스를 구현한 클래스에서 정의한다.

  • 설계시 선언해 두면 개발할 때 기능을 구현하는 데에만 집중할 수 있다.
  • 개발자의 역량에 따른 메서드의 이름과 매개 변수 선언의 격차를 줄일 수 있다.
  • 공통적인 인터페이스와 abstract 클래스를 선언해 놓으면, 선언과 구현을 구분할 수 있다.

인터페이스는 여러개 구현할 수 있다.

인터페이스를 구현할 경우 인터페이스에 정의된 메서드를 완성 시켜야 한다. 즉 메서드를 구현해야 한다.

인터페이스의 모든 메서드를 구현해야 컴파일이 수행된다.

또한, 반드시 클래스 형변환을 사용해야 한다.

MemberManagerInterface member = new MemberManagerImpl();

즉 인터페이스 타입으로 선언 후, 실제 객체할당은 구현체를 할당하는 것이다.

abstract

abstract 는 클래스와 메서드에 붙일 수 있는 예약어이다.

abstract로 선언한 메서드가 하나라도 있으면, abstract 클래스로 선언한다.

인터페이스와 달리 구현되어 있는 메서드가 있어도 상관없다.

abstract 클래스는 static 이나 final 메서드가 있어도 된다.

인터페이스와 마찬가지로 abstract 메서드들을 구현 클래스에서 재정의 해야 한다.

클래스 형변환을 사용하지만, 사용하지 않는다면 abstarct 메서드는 사용할 수 없다.

final

final은 클래스, 메서드, 변수에 선언할 수 있다.

클래스에 final을 선언한다면 상속할 수 없다. 즉 다른 클래스가 final 클래스를 상속 할 수 없다.

메서드에 final을 선언한다면 하위 클래스에서 오버라이딩 할 수 없다.

변수에 final을 선언한다면 초기화 이후는 값을 변경할 수 없다. 인스턴스 변수에 사용하려면 반드시 선언과 동시에 초기화를 해줘야 한다.

Enum

변수에 final을 선언한다면 값을 고정할 수 있다. 이런 것을 상수라고 한다.

클래스가 상수로만 이루어져 있다면, 반드시 class로 선언할 필요가 없다.

이럴 때 enum을 사용하면, 이 객체는 상수의 집합이라는 것을 나타낸다.

enum 클래스는 어떻게 보면 타입이다. enum 클래스이름.상수 이름 을 지정함으로써 클래스의 객체 생성이 완료된다.

enum 클래스 내부에 변수를 선언해 사용할 수도 있다.

enum 클래스의 부모는 java.lang.Enum 클래스이다. 다른 일반 클래스들은 상속에 상속을 거쳐서 결국 Object 클래스를 상속받는다.

하지만 enum 클래스는 상속을 받지 못한다. Enum 클래스는 컴파일러가 알아서 문장을 추가해주고 컴파일 해준다.

결국 Enum 클래스의 부모는 Object 이지만, 4개의 메서드를 오버라이딩 불가하도록 final로 선언되어 있다.

  • clone()
  • finalize()
  • hashCode()
  • equals()

enum 클래스 객체는 hashCode()와 equals()는 사용이 가능하다.

  • compareTo(E e) : 순서 차이 리턴
  • getDeclaringClass() : 클래스 타입의 enum 리턴
  • name() : 상수 이름 리턴
  • ordinal() : 상수의 순서 리턴
  • valueOf(Class<T> enumType, String name) : static 메서드
  • values() : enum 클래스에 선언되어 있는 모든 상수를 배열로 리턴

Network

IP 주소의 이용 범위

IP 주소는 이용 범위에 따라 공인 IP 주소와 사설 IP 주소로 나눠진다.

공인 주소는 인터넷에서 이용하는 IP 주소이다. 인터넷에서 통신하기 위해서는 반드시 공인 주소가 필요하다.

공인 주소는 인터넷 전체에서 중복되지 않도록 관리된다.

사내 네트워크 등 사설 네트워크에서 사용하는 IP 주소가 사설 IP 주소이다.

사설 IP 주소의 범위는 다음과 같다.

  • 10.0.0.0 ~ 10.255.255.255
  • 172.16.0.0 ~ 172.31.255.255
  • 192.168.0.0 ~ 192.196.255.255

위 범위 주소는 사설 네트워크 안이라면 자유롭게 이용할 수 있다. 다른 네트워크의 사설 주소와 겹치더라도 사설 네트워크 안의 통신에는 문제가 없다.

사설 네트워크에서 인터넷 통신

사설 주소를 이용하면 사설 네트워크에서 인터넷으로 통신할 때는 사설 주소 그대로 사용할 수 없기 때문에, NAT(Network Address Translation)이 필요하다.

사설 주소 그대로는 응답이 돌아오지 않음

사설 네트워크에서 인터넷으로 그대로 통신할 수 없다.

사설 네트워크에서 인터넷의 서버에 요청을 보내면, 목적지는 공인 주소이고, 출발지는 사설 네트워크이여서, 요청은 서버로 보낼 수 있다.

하지만 응답은 불가능하다. 인터넷에서 목적지가 사설 주소로 된 IP 패킷은 폐기되기 때문이다.

주소를 변환

사설 네트워크에서 인터넷으로 통신하기 위해서는 NAT으로 주소를 변환해야 한다.

  1. 사설 네트워크에서 인터넷으로 요청을 전송할 때, 출발지 IP 주소를 변환한다.
  2. 라우터는 나중에 원래대로 되돌리기 위해 변환한 주소의 대응을 NAT 케이블에 보존해둔다.
  3. 요청에 대한 응답이 라우터로 돌아오면, 목적지 IP 주소를 변환한다. 이때 NAT 테이블에 보존해 둔 주소의 대응을 이용한다.

사설 주소와 공인 주소를 1:1 대응하려면, 공인 주소가 많이 필요해진다.

복수의 사설 주소를 하나의 글로벌 주소에 대응시키는 주소 변환을 NAPT(Network Address Port Translation)라고 부른다.

IP는 확인하지 않는다

IP로 데이터를 다른 호스트에 전송하지만, IP에는 제대로 도착했는지 확인할 방법이 없다.

즉, 전송하는 데이터에 IP 헤더를 붙여 IP 패킷으로 만들어 네트워크 상에 내보내기만 할 뿐이다.

목적지 까지 도달하면 그 응답이 돌아오지만, 목적지까지 돌아오지 않았다면 응답이 돌아오지 않고, 도달하지 못한 이유도 알 수 없다.

이런 IP를 이용하는 데이터 전송의 특징을 최선형(best effort)라고 한다.

데이터를 보내기 위해 최선을 다하겠지만, 안되면 어쩔 수 없는게 IP의 특징이다.

ICMP

IP에 의한 엔드투엔드 통신이 정상적으로 이루어졌는지 확인하는 기능을 갖춘 프로토콜 ICMP(Internet Control Message Protocol)가 개발되었다.

  • 에러 리포트
  • 진단 기능

어떤 이유로 IP 패킷을 폐기했다면, 폐기한 기기가 ICMP를 이용해 폐기한 IP 패킷의 출발지로 에러 리포트를 전송한다.

에러 리포트를 도달 불능 메시지라 부른다. 도달 불능 메시지로 엔드투엔드 통신에 실패한 원인을 통지한다.

진단 기능은 IP의 엔드투엔드 통신이 가능한지 확인하는 기능이다.

진달을 위해서 자주 이용하는 명령은 ping 커맨드가 있다.

ping 커맨드로 ICMP 에코 요청/응답 메시지를 보내서, 지정한 IP 주소와 통신할 수 있는지 확인한다.

ARP란?

TCP/IP에서 IP주소를 지정해 IP 패킷을 전송한다. IP 패킷은 PC나 서버 등의 인터페이스까지 전송된다.

PC나 서버 등의 인터페이스는 MAC 주소로 식별한다.

ARP는 IP 주소와 인터페이스를 식별하기 위한 MAC 주소를 대응시킨다.

이더넷 인터페이스에서 IP 패킷을 내보낼 때는 이더넷 헤더를 붙인다. 이더넷 헤더에는 목적지 MAC 주소를 지정해야 한다.

목적지 IP 주소에 대응하는 MAC 주소를 구하기 위해 ARP를 이용한다.

또한 IP 주소와 MAC 주소를 대응시키는 것을 가리켜 주소 해석이라 한다.

ARP 동작 흐름

ARP의 주소 해석 범위는 같은 네트워크 내의 IP 주소이다.

이더넷 인터페이스로 접속된 PC 등의 기기가 IP 패킷을 송신하고자 목적지 IP 주소를 지정할 때 자동으로 ARP가 수행된다.

즉, ARP로 주소 해석을 한다.

  1. ARP 요청으로 IP 주소에 대응하는 MAC 주소를 질의한다.
  2. 질의받은 IP 주소를 가진 호스가 ARP 응답으로 MAC 주소를 알려준다.
  3. 주소 해석한 IP 주소와 MAC 주소의 대응을 ARP 캐시에 보존한다.

포트번호의 역할

호스트에서 동작하는 애플리케이션에 데이터를 배분하기 위해서는 각각의 애플리케이션을 식별할 수 있어야 한다.

애플리케이션을 식별하는 데에는 포트 번호를 이용한다.

포트번호란 TCP/IP의 애플리케이션을 식별하는 식별 번호로 TCP, UDP 헤더에 지정한다.

포트번호는 16비트 수치이므로, 0 ~ 65535 범위를 가진다.

  • 웰노운 포트 : 0 ~ 1023 : 서버 애플리케이션 용으로 예약된 포트 번호
  • 등록된 포트 번호 : 1024 ~ 49151 : 자주 이용되는 애플리케이션의 서버 쪽 포트 번호
  • 동작/사설 포트 : 49152 ~ 65535 : 클라이언트 애플리케이션용 포트 번호

웰노운 포트 번호로 웹 브라우저 요청을 기다림

웰노운 포트 번호는 미리 정해져 있다.

서버 애플리케이션을 실행하면, 웰노운 포트 번호로 클라이언트 애플리케이션의 요청을 기다린다.

  • HTTP : 80
  • HTTPS : 443
  • SMTP : 25
  • POP3 : 110
  • IMAP4 : 143
  • FTP : 20/21
  • DHCP : UDP 67/68

등록된 포트로 식별한다.

등록된 포트는 웰노운 포트 이외에 자주 이용되는 서버 애플리케이션을 식별하기 위한 포트 번호. 등록된 포트도 미리 정해져 있다.

동작/사설 포트로 식별한다.

동작/사설 포트는 클라이언트 애플리케이션을 식별하기 위한 포트 번호이다.

미리 정해져 있지 않고, 클라이언트 애플리케이션이 통신할 때 동적으로 할당된다.


Linux

OS는 커널 이외에도 사용자 모드에서 동작하는 다양한 프로그램으로 구성되어 있다.

프로그램은 라이브러리 형태도 있고, 단독 프로그램으로 동작하는 것도 있다.

사용자 모드에서는 프로세스의 고유 코드는 라이브러리의 함수를 호출하거나 시스템 콜을 호출한다. 또한 라이브러리가 시스톰 콜을 호출할 수도 있다.

시스템 콜

프로세스는 프로세스의 생성이나 하드웨어의 조작 등 커널의 도움이 필요할 경우 시스템 콜을 통해 커널에 처리를 요청한다.

시스템 콜의 종류

  • 프로세스 생성, 삭제
  • 메모리 확보, 해제
  • 프로세스 간 통신(IFC)
  • 네트워크
  • 파일시스템 다루기
  • 파일 다루기(디바이스 접근)

CPU 모드 변경

시스템 콜은 CPU의 특수한 명령을 실행해야만 호출된다.

프로세스는 보통 사용자 모드로 실행되고 있지만, 커널에 처리를 요청하고자 시스템 콜을 호출하면 CPU에서는 인터럽트 이벤트가 발생한다.

인터럽트 이벤트가 발생하면 CPU는 사용자 모드에서 커널 모드로 변경 되며, 요청한 내용을 처리하기 위해 커널은 동작하기 시작한다.

요청한 내용 처리가 끝나면 커널 내의 시스템 콜 처리가 종료되고, 다시 사용자 모드로 돌아가 프로세스의 동작을 계속 진행한다.

커널은 프로세스가 요청한 내용을 처리하기 전에 프로세스의 요구가 유효한지 확인하고, 요구사항이 맞지 않으면 실패 했다고 처리한다.

유저 프로세스에서 시스템콜을 통하지 않고 직접 CPU 모드를 변경할 수는 없다.

시스템 콜 호출의 동작 순서

#include <stdio.h>

int main(void){
	put("hello world");
	return 0;
}

컴파일

$ cc -o hello hello.c
$ ./hello
hello world
$

시스템 콜 디버깅

$strace -o hello.log ./hello
hello world
$
$ cat hello.log
execve("./hello", ["./hello"], 0xffffc36b2e00 /* 9 vars */) = 0
brk(NULL)                               = 0xaaaac2b07000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff86daf000
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=9735, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 9735, PROT_READ, MAP_PRIVATE, 3, 0) = 0xffff86dac000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\\177ELF\\2\\1\\1\\3\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\267\\0\\1\\0\\0\\0\\340u\\2\\0\\0\\0\\0\\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=1641496, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 1810024, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff86bc0000
mmap(0xffff86bc0000, 1744488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86bc0000
munmap(0xffff86d6a000, 65128)           = 0
mprotect(0xffff86d49000, 61440, PROT_NONE) = 0
mmap(0xffff86d58000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x188000) = 0xffff86d58000
mmap(0xffff86d5e000, 48744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff86d5e000
close(3)                                = 0
set_tid_address(0xffff86daff30)         = 1526
set_robust_list(0xffff86daff40, 24)     = 0
rseq(0xffff86db0600, 0x20, 0, 0xd428bc00) = 0
mprotect(0xffff86d58000, 16384, PROT_READ) = 0
mprotect(0xaaaaaf8a0000, 4096, PROT_READ) = 0
mprotect(0xffff86db4000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0xffff86dac000, 9735)            = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
getrandom("\\x18\\x5b\\x8e\\x33\\xbe\\xf8\\x28\\x37", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0xaaaac2b07000
brk(0xaaaac2b28000)                     = 0xaaaac2b28000
write(1, "hello world\\n", 12)           = 12 ----> 1.
exit_group(0)                           = ?
+++ exited with 0 +++

strace 각각의 줄은 1개의 시스템 콜 호출이다.

1의 출력을 보면 write() 시스템 콜이 화면에 문자열을 출력한다.

어떤 언어로 작성되도, 프로그램이 커널에 처리를 요청할 때는 결국 시스템 콜을 호출한다.

프로세스가 사용자 모드와 커널 모드 중 어느 쪽에서 실행되고 있는지의 비율은 sar 명령어로 확인 가능하다.

root@cce021236ef5:/com# sar -P ALL 1
Linux 5.15.49-linuxkit (cce021236ef5) 	08/06/23 	_aarch64_	(5 CPU)

07:48:02        CPU     %user     %nice   %system   %iowait    %steal     %idle
07:48:03        all      1.62      0.00      0.61      0.00      0.00     97.77
07:48:03          0      1.01      0.00      1.01      0.00      0.00     97.98
07:48:03          1      1.02      0.00      0.00      0.00      0.00     98.98
07:48:03          2      1.02      0.00      1.02      0.00      0.00     97.96
07:48:03          3      3.03      0.00      1.01      0.00      0.00     95.96
07:48:03          4      2.00      0.00      0.00      0.00      0.00     98.00

07:48:03        CPU     %user     %nice   %system   %iowait    %steal     %idle
07:48:04        all      3.62      0.00      1.81      0.20      0.00     94.37
07:48:04          0      4.04      0.00      2.02      0.00      0.00     93.94
07:48:04          1      3.03      0.00      2.02      0.00      0.00     94.95
07:48:04          2      3.03      0.00      1.01      0.00      0.00     95.96
07:48:04          3      4.00      0.00      2.00      1.00      0.00     93.00
07:48:04          4      4.00      0.00      2.00      0.00      0.00     94.00

 

한 줄에 1개의 CPU 코어가 대응하고 있고, 코어에 대해 정보가 출력된다.

사용자 모드에서 프로세스를 실행하고 있는 시간의 비율은 %user와 $nice의 합계로 얻을 수 있다.

CPU 코어가 커널 모드에서 시스템 콜 들이 처리를 실행하고 있는 시간의 비율은 %system 으로 얻을 수 있다.

%idle은 유후상태를 의미한다.

sar -P ALL 1 1 마지막 네번째 파라미터에 측정 횟수를 지정할 수 있다.

무한 루프 측정

int main(void){
	for(;;)
	;
}
$ cc -o loop loop.c
$ ./loop &
[1] 2267 ------> pid
root@cce021236ef5:/com# sar -P ALL 1 1
Linux 5.15.49-linuxkit (cce021236ef5) 	08/06/23 	_aarch64_	(5 CPU)

07:53:29        CPU     %user     %nice   %system   %iowait    %steal     %idle
07:53:30        all     20.60      0.00      0.40      0.00      0.00     79.00
07:53:30          0      0.00      0.00      0.00      0.00      0.00    100.00
07:53:30          1      1.01      0.00      0.00      0.00      0.00     98.99
07:53:30          2    100.00      0.00      0.00      0.00      0.00      0.00
07:53:30          3      0.99      0.00      0.99      0.00      0.00     98.02
07:53:30          4      0.00      0.00      1.00      0.00      0.00     99.00

Average:        CPU     %user     %nice   %system   %iowait    %steal     %idle
Average:        all     20.60      0.00      0.40      0.00      0.00     79.00
Average:          0      0.00      0.00      0.00      0.00      0.00    100.00
Average:          1      1.01      0.00      0.00      0.00      0.00     98.99
Average:          2    100.00      0.00      0.00      0.00      0.00      0.00
Average:          3      0.99      0.00      0.99      0.00      0.00     98.02
Average:          4      0.00      0.00      1.00      0.00      0.00     99.00

시스템콜 무한루프

#inlcude <sys/types.h>
#include <unistd.h>

int main(void){
	for(;;)
		getppid();
}
root@cce021236ef5:/com# cc -o ppidloop ppidloop.c
root@cce021236ef5:/com# ./ppidloop &
[1] 2277
root@cce021236ef5:/com# sar -P ALL 1 1
Linux 5.15.49-linuxkit (cce021236ef5) 	08/06/23 	_aarch64_	(5 CPU)

07:56:46        CPU     %user     %nice   %system   %iowait    %steal     %idle
07:56:47        all      9.42      0.00     11.82      0.00      0.00     78.76
07:56:47          0      1.00      0.00      1.00      0.00      0.00     98.00
07:56:47          1      1.01      0.00      0.00      0.00      0.00     98.99
07:56:47          2     43.00      0.00     57.00      0.00      0.00      0.00
07:56:47          3      1.00      0.00      0.00      0.00      0.00     99.00
07:56:47          4      1.00      0.00      1.00      0.00      0.00     98.00

Average:        CPU     %user     %nice   %system   %iowait    %steal     %idle
Average:        all      9.42      0.00     11.82      0.00      0.00     78.76
Average:          0      1.00      0.00      1.00      0.00      0.00     98.00
Average:          1      1.01      0.00      0.00      0.00      0.00     98.99
Average:          2     43.00      0.00     57.00      0.00      0.00      0.00
Average:          3      1.00      0.00      0.00      0.00      0.00     99.00
Average:          4      1.00      0.00      1.00      0.00      0.00     98.00
root@cce021236ef5:/com# kill 2277

cpu 2 내에서는 ppidllop 프로그램을 43% 비율로 실행하고, 이 프로그램이 부모 프로세스를 얻는 커널의 처리를 57% 비율로 실행하고 있었다.

대체로 %system의 수치가 크면 시스템 콜이 너무 많이 호출되거나, 시스템에 과부하가 걸려 있는 상태이다.

시스템 콜의 소요 시간

strace에 -T 옵션을 붙이면 시스템 콜 처리에 걸린 시간을 마이크로초 단위로 정밀하게 측정할수 있다.

%system의 점유율이 높을 때 디버깅 용도로 사용하면 편하다.

root@cce021236ef5:/com# strace -T -o hello.log ./hello
hello world
[1]+  Terminated              ./ppidloop
root@cce021236ef5:/com# cat hello.log
execve("./hello", ["./hello"], 0xfffff7c82f38 /* 9 vars */) = 0 <0.000608>
brk(NULL)                               = 0xaaaaebbb8000 <0.000161>
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff9f2a0000 <0.000065>
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000074>
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000116>
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=11147, ...}, AT_EMPTY_PATH) = 0 <0.000153>
mmap(NULL, 11147, PROT_READ, MAP_PRIVATE, 3, 0) = 0xffff9f29d000 <0.000123>
close(3)                                = 0 <0.000104>
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000139>
read(3, "\\177ELF\\2\\1\\1\\3\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0\\267\\0\\1\\0\\0\\0\\340u\\2\\0\\0\\0\\0\\0"..., 832) = 832 <0.000101>
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=1641496, ...}, AT_EMPTY_PATH) = 0 <0.000095>
mmap(NULL, 1810024, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff9f0b1000 <0.000121>
mmap(0xffff9f0c0000, 1744488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff9f0c0000 <0.000111>
munmap(0xffff9f0b1000, 61440)           = 0 <0.000114>
munmap(0xffff9f26a000, 3688)            = 0 <0.000097>
mprotect(0xffff9f249000, 61440, PROT_NONE) = 0 <0.000132>
mmap(0xffff9f258000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x188000) = 0xffff9f258000 <0.000105>
mmap(0xffff9f25e000, 48744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff9f25e000 <0.000096>
close(3)                                = 0 <0.000120>
set_tid_address(0xffff9f2a0f30)         = 2283 <0.000092>
set_robust_list(0xffff9f2a0f40, 24)     = 0 <0.000091>
rseq(0xffff9f2a1600, 0x20, 0, 0xd428bc00) = 0 <0.000116>
mprotect(0xffff9f258000, 16384, PROT_READ) = 0 <0.000111>
mprotect(0xaaaae0e30000, 4096, PROT_READ) = 0 <0.000102>
mprotect(0xffff9f2a5000, 8192, PROT_READ) = 0 <0.000108>
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 <0.000095>
munmap(0xffff9f29d000, 11147)           = 0 <0.000106>
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0 <0.000097>
getrandom("\\xd6\\x23\\x0c\\xf3\\x1c\\x7d\\x83\\x6c", 8, GRND_NONBLOCK) = 8 <0.000115>
brk(NULL)                               = 0xaaaaebbb8000 <0.000094>
brk(0xaaaaebbd9000)                     = 0xaaaaebbd9000 <0.000100>
write(1, "hello world\\n", 12)           = 12 <0.000091>
exit_group(0)                           = ?
+++ exited with 0 +++

시스템 콜의 wrapper 함수

리눅스에는 프로그램의 작성을 도와주기 위해 프로세스 대부분에 필요한 여러 라이브러리 함수가 있다.

시스템 콜은 보통의 함수 호출과는 다르게 c 언어 등 고급 언어에서는 직접 호출이 불가능하다.

아키텍처에 의존하는 어셈블리 코드를 사용해 호출해야 한다.

즉, OS에 도움이 없다면 각 프로그램은 시스템 콜을 호출할 때마다 아키텍처에 의존하는 어셈블리 언어를 써어 고급언어로부터 어셈블리 코드를 호출해야 한다.

위 방식은 다른 아키텍처에 이식도 불가능해 이식성도 낮고, 프로그램을 작성하는데 오래걸린다.

위 문제를 해결하기 위해 OS는 내부적으로 시스템 콜을 호출하는 일만 하는 함수를 제공하는데 이를 시스템 콜 wrapper 라고 한다.

wrapper 함수는 아키텍처 별로 존재하고, 각 언어에 대응해 주닙된 시스템 콜의 wrapper 함수를 호출하기만 하면 된다.

표준 C 라이브러리

C 언어에는 ISO에 의해 정해진 표준 라이브러리가 있다.

리눅스에도 표준 C 라이브러리가 제공된다.

GNU 프로젝트가 제공하는 glibc는 wrapper 함수를 포함하고, POSIX 규격에 정의된 함수도 제공한다.

  • POSIX 규격 : 유닉스 계열의 OS가 갖춰야할 각종 기능 규격
root@cce021236ef5:/com# ldd /bin/echo
	linux-vdso.so.1 (0x0000ffffadc73000)
	libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffada70000)
	/lib/ld-linux-aarch64.so.1 (0x0000ffffadc3a000)

libc 부분이 표준 C 라이브러리이다.

C언어는 OS 레벨에서 매우 중요한 언어이다.

리눅스는 이외에도 여러가지 프로그래밍 언어의 표준 라이브러리를 제공하고, 비표준 라이브러리도 있다.

OS가 제공하는 프로그램

OS가 제공하는 프로그램은 OS가 제공하는 라이브러리와 마찬가지로 프로그램이 필요로 한다.

OS의 동작을 변경 시키는 프로그램도 OS의 일부로서 제공된다.

  • 시스템 초기화 : init
  • OS의 동작을 바꿈 : sysctl, nice, sync
  • 파일 관련, touch, mkdir
  • 텍스트 데이터 가공 : grep, sort, uniq
  • 성능 측정 : sar, iostat
  • 컴파일러 : gcc
  • 스크립트 언어 실행 환경 : perl, python, ruby
  • 쉘 : bash
  • 윈도우 시스템 : X

'Back-end' 카테고리의 다른 글

2023.08.08 TIL  (0) 2023.08.09
2023.08.07 TIL  (0) 2023.08.07
2023.08.04 TIL  (0) 2023.08.04
2023.08.02 TIL  (0) 2023.08.02
2023.08.01 TIL  (0) 2023.08.01