-
리눅스 - 라이브러리CS지식/운영체제 2024. 1. 14. 16:56
리눅스
OS가 제공하는 라이브러리(library)를 살펴보자. 프로그래밍 언어는 다수의 프로그램에서 공통으로 사용하는 처리를 라이브러리로 합쳐서 제공하는 기능이 있다. 이걸 사용해서 프로그래머는 미리 만들어지 대량의 라이브러리에서 필요한 걸 골라서 효율적으로 프로그램을 개발할 수 있다. 라이브러리 중에는 OS가 미리 공통된 기능을 가진 라이브러리를 준비해서 제공하는 경우도 있다.
프로세스가 라이브러리를 사용할 때 소프트 웨어 계층은 [그림 01-06]과 같다.
그림 01 - 06 프로세스의 소프트웨어 계층
표준 C 라이브러리
C언어는 국제 표준화 기구(ISO)에서 정한 표준 라이브러리가 존재한다. 리눅스에서도 이런 표준 C라이즈러리가 제공된다. 일반적으로 GNU 프로젝트 에서 제공하는 glibc를 표준 C라이브러리 사용합니다. 보통 glibc를 libc라고 표기한다.
C언어로 작성된 대부분의 C프로그램은 libc를 링크(linking)한다.
프로그램이 어떤 라이브러리를 링크하는지 알아보면 ldd 명령어를 사용해서 확인하면 된다. echo 명령어에 ldd 명령어를 실행한 결과를 살펴보자.
$ ldd /bin/echo linux-vdso.so.1 (0x00007ffd3eecb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fad4ca82000) /lib64/ld-linux-x86-64.so.2 (0x00007fad4ccbd000)
출력 결과에서 libc.so.6은 표준 C라이브러리를 뜻한다. 그리고 ld-linux-x84-64.so.2는 공유 라이브러리를 로드하는 특별한 라이브러리이다. 이것도 OS가 제공하는 라이브러리 중 하나이다.
cat명령어도 살펴보자 .
$ ldd /bin/cat linux-vdso.so.1 (0x00007fffa49ed000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb665740000) /lib64/ld-linux-x86-64.so.2 (0x00007fb66597b000)
이번에도 마찬가지로 libc를 링크하고 있다. 파이썬3을 실행하는 python3 명령어도 확인해보자.
$ ldd /usr/bin/python3 linux-vdso.so.1 (0x00007ffcacd8c000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2551215000) libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f25511e4000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f25511c8000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2550f9f000) /lib64/ld-linux-x86-64.so.2 (0x00007f25518ec000)
이것도 마찬가지로 libc를 링크라고 이다. 즉 파이썬 프로그램을 실행할 때 내부적으로는 표준 C라이브러리를 사용한다. 최근에는 C언어를 직접 사용하는 일이 드물어졌다고 하지만 OS수준에서는 안 보이는 곳에서 막강한 힘을 발휘하는 여전히 중요한 언어라는 걸 알 수 있다.
리눅스 이외에도 C++같은 다양한 프로그래밍 언어의 표준 라이브러리를 제공한다. 뿐만 아니라 표준 라이브러리는 아니지만 프로그래머가 자주 사용하는 라이브러리도 많이 있다.
$ dpkg-query -W | grep ^lib
우분투 라이브러리 파일은 lib라는 이름으로 시작하는 경우가 많다. 위 명령어는 lib로 시작하는 패키지를 검색할 수 있는 명령어이다.
시스템 콜 래퍼 함수
libc는 표준 C 라이브러리뿐만 아니라 시스템 콜 래퍼(wrapper)함수도 제공한다. 시스템 콜은 일반 함수 호출과 다르게 C 언어 같은 고급 언어에서 직접 호출할 수 없다. 아키텍처에 의존하는 에셈블리 코드를 사용해서 호출해야한다.
예를 들어 x86_64 아키텍쳐 CPU라면 getppid()시스템 콜은 어셈블리 코드 레벨에서 다음 과 같이 호출한다.
mov $0x6e, %eax syscall
첫 번째 줄은 getppid()의 시스템 콜 번호 0x6e를 eax 레지스터에 대입한다. 이건 리눅스 시스템 콜 호출 규약에 정해진 내용이다. 이어 두 번째 줄은 syscall 명령으로 시스템 콜을 호출하고 커널 모드로 전환한다. 그런 다음에 getppid()를 처리하는 커널 코드가 실행된다. 평소에 어셈블리 언어를 볼 기회가 없었다면 코드 내용이 어떤 의미인지 자세히 이해할 필요 없이, 평소에 보던 코드와 완전 다르다는 분위기만 느끼면 충분하다.\
스마트폰이나 태블릿에서 주로 사용하는 arm64 아키텍쳐는 어셈블리 코드 레벨에서 getppid()시스템 콜을 다음처럼 호출한다.
mov x8, <시스템 콜 번호> svc #0
앞에서 본 코드와 많이 다르다. 만약 libx의 도움이 없었다면 시스템 콜을 호출할 때마다 아키텍처 의존 어셈블리 코드를 작성해서 고급 언어에서 계속 호출해야한다.(그림 01-07)
그림 01-07 만약 OS 도움이 없었다면
이렇게 되면 프로그램 작성이 번거롭고 다른 아키텍처로 이식할 때 동작을 보장할 수 없는 등 여러문제가 생긴다. 이런 문제점을 해결하기 위해 libc는 내부적으로 시스템 콜을 호출할 뿐 인 시스템 콜 래퍼 함수를 제공한다. 래퍼 함수는 아키텍처별로 존재한다. 고급 언어로 작성된 사용자 프로그램에서는 언어마다 준비된 시스템 콕의 래퍼 함수를 호출하기만 하면 된다. (그림 01-08).
그림 01-08 사용자 프로그램은 래퍼 함수를 호출하면 끝
정적 라이브러리와 공유 라이브러리
라이브러리는 정적(static) 라이브러리 와 공유(shared) 또는 동적(dynamic) 라이브러리 두 종류로 분류할 수 있다. 모두 같은 기능을 제공하지만 프로그램과 결합하는 방식이 다른다.
프로그램을 생성하려면 1)우선 소스 코드를 컴파일해서 오브젝트(Object)파일을 만든다. 그리고 2)오브젝트 파일이 사용하는 라이브러리를 링트해서 실행 파일을 만든다. ⭐정적 라이브러리는 링크할 때 라이브러리에 있는 함수를 프로그램에 집어 넣는다. 반면에 ⭐공유 라이브러리는 링크할 때 '이 라이브러리의 이런 함수를 호출한다'라는 정보만 실행 파일에 포함한다. 그리고 프로그램이 시작하거나 실행 중에 라이브러리를 메모리에 로드(load)하고 프로그램은 그 안에 있는 함수를 호출한다.
하는일 없이 pause() 시스템 콜만 호출하는 pause.c 프로그램(코드 01-05)을 통해 각 라이브러리 차이점을 설명하는 [그림 01-09]를 살펴보자.
그림 01-09 정적 라이브러리와 공유 라이브러리
코드 01-05 pause.c
#include <unistd.h> int main(void){ pause(); return 0; }
코드 실행 후 [그림 01-09]와 같이 되는지 다음 관점에서 확인해 보자.
- 파일 크기
- 공유 라이브러리와 링크 상태
예를 들어 프로그램에 libc를 링크하는 경우를 생각해 보자. 우선 libc 정적 라이브러리인 libc.a를 사용한 경우를 확인한다.
실행 결과에서 다음을 알 수 있다.
1️⃣ 프로그램 크기는 900KiB정도
2️⃣ 공유 라이브러리는 링크되어 있지 않음.
즉 프로그램은 libc를 이미 포함하브로 lib.a를 삭제해도 동작하겠지만, 그렇게 되면 이후에 다른 프로그램이 libc와 정적 링크할 수 없게 되므로 실제로 삭제하는 건 무척 위험한 행동이다.
이어서 공유 라이브러리 lib.so를 사용한 경우를 살펴보자
실행 결과에서 다음을 알수 있다.
- 파일크기는 16KiB정도로 libc를 정적 링크했을 때 보다 수십 분의 일 크기
- libc(/lib/x86_64-linux-gnu/libc.so.6)를 동적 링크함
libc를 동적 링크한 pause 프로그램은 libc.so를 삭제하면 실행이 안 된다. 게다가 libc.so를 링크하는 프로그램도 모두 실행 불가능해져서 libc.a를 삭제했을 대보다 훨씬 심각한 일이 발생한다.
파일 크기가 작은 이유는 libc가 프로그램 자체에 포함되는 게 아니라 실행 시 메모리에 로드되기 때문이다. libc 코드는 프로그램마다 각각의 복사본을 사용하는 대신에 bic를 사요하는 모든 프로그램에 같은 내용을 공유한다.
정적 라이브러리와 공유 라이브러리는 모두 장단점이 있으므로 어느 쪽이 꼭 좋다고 단정을 지을 수 없지만, 다음과 같은 이유로 공유 라이브러리를 자주 사용한다.
- 시스템에서 차지하는 크기를 줄일 수 있다.
- 라이브러리에 문제가 있을 때 공유 라이브러리를 수정 버전으로 교체하기만 하면 해다 라이브러리를 사용하는 모든 프로그램에서 문제가 수정가능하다.
사용하는 프로그램 실행 파일에 어떤 공유라이브러리가 사용되었는지 궁금할땐 ldd명령어를 실행해 어떤 공유 라이브러리가 링크되어 있는지 확인하자
[출처 - 그림으로 배우는 리눅스 구조 , 저 다케우치 사토루 ]
https://www.hanbit.co.kr/store/books/look.php?p_code=B9151150768
'CS지식 > 운영체제' 카테고리의 다른 글
운영체제1 - 운영체제 소개(기본개념, 발전목적,기능) (0) 2024.07.11 하드웨어 레벨 - 메인보드 (1) 2024.01.30 리눅스 - 프로그램과 프로세스, 커널, 시스템 콜 (1) 2024.01.14 운영체제 - OS, Operating System (0) 2023.11.02 서버 기술 기초 요약 - 리눅스 쉘 사용법 6 (0) 2022.05.01