Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

정석원의 블로그

3장 시스템 프로그래밍 개념 본문

리눅스

3장 시스템 프로그래밍 개념

seokwon jeong 2018. 1. 16. 14:26

시스템 호출 소개 및 각 시스템 호출이 실행될 때 일어나는 단계 확인하는 파트


시스템 호출이나 라이브러리 함수 호출을 할 때마다, 언제나 호출이 성공적이었는지를 알기 위해 그 리턴 상태를 확인해야 한다.



3.1 시스템 호출


시스템 호출은 커널로의 관리된 진입점으로서, 이를 통해 프로세스가 커널에게 프로세스 대신 어떤 동작을 수행하도록 요청할 수 있다. 커널은 시스템 호출 API를 통해 광범위한 서비스를 제공하는데 새로운 프로세스 생성, 프로세스간 통신용 파이프 생성 등이 있다.


시스템 호출의 일반적인 사항


  • 시스템 호출은 프로세서의 상태를 사용자 모드에서 커널 모드로 변경해서 CPU가 보호된 커널 메모리에 접근할 수 있게 한다.
  • 시스템 호출 목록은 고정되어 있다. 각 시스템 호출에는 고유한 숫자가 붙어 있다.
  • 각 시스템 호출은 사용자 공간에서 커널 공간으로 가져올 정보를 나타내는 인자들을 가질 수 있다.

간단한 시스템 호출이라도 많은 작업이 필요하고, 따라서 작지만 주목할 만한 오버헤드가 있다.
ex) 단순 호출 함수인 getppid()를 천만번 수행하는데 2.2초나 걸린다. 하지만 정수를 리턴하는 c 함수를 천만번 수행하는데 0.11초 밖에 걸리지 않는다. !


3.2 라이브러리 함수

라이브러리 함수는 표준 C 라이브러리를 구성하는 수많은 함수 가운데 하나일 뿐이다. (파일 열기, 시간 포맷 바꾸기, 문자열 비교 등)
시스템 호출을 써서 구현된 라이브러리 함수도 있는데(fopen() 같은) 하부의 시스템 호출보다 사용하기 편리한 인터페이스를 제공하도록 설계됐다.


3.3 표준 C 라이브러리 : GNU C 라이브러리

유닉스 구현에 따라 표준C 라이브러리의 구현이 다를 수 있는데, 가장 널리 쓰이는 구현은 GNU C(glibc) 라이브러리 이다.

시스템 glibc 버전 판별하기 명령어

$ /lib/libc.so.6


없을때 위치를 찾는 명령어 ==> (centos 7 에선 왜 안될까요 ?)


$ ldd myprog | grep libc


프로그램에서 버전을 알아내는 방법도 존재한다.

 -> const char *gnu_get_libc_version(void);



3.4 시스템 호출과 라이브러리 함수의 에러 처리


대부분의 시스템 호출과 라이브러리 함수가 성공 여부를 나타내는 상태값을 리턴한다. 이것은 항상 확인하여야 한다! 상태값을 확인하지 않아 많은 디버그시간을 낭비하게 될 수 있다.


시스템 호출 에러 처리


fd = open(pathname, flags, mode);

if(fd == -1){

에러 처리 코드

}

...

if(close(fd) == -1){

에러 처리 코드

}


시스템 호출이 실패하면 전역 변수 errno를 특정 에러를 나타내는 양수로 설정하는데, 헤더 파일 errno.h 을 사용하면 된다.


cnt = read(fd, buf, numbytes);

if(cnt == -1){

if(errno == EINTR){

fprintf(stderr, "read was interrupted by a signal\n");

}else{

기타 에러

}

}


이전 함수 호출에 의해 errno가 0이 아닌 값을 갖고 있을 수 있으니, 함수의 리턴값을 먼저 확인하고, errno를 확인하도록 하자.(성공적인 시스템 호출과 라이브러리 함수는 절대로 errno를 0으로 설정하지 않거든 ㅎ)


몇몇 시스템 호출은(getpriority()) 성공 시에도 -1를 리턴할 수 있으니 errno를 0으로 설정하고 호출 후 다시 확인해야 한다.


시스템 호출이 실패하면 errno 값에 따라 메시지를 출력해주고, perror() 나 strerror()를 사용하면 편하다.


라이브러리 함수의 에러 처리는 다양한 경우가 많기 때문에 각 함수의 메뉴얼 페이지를 확인하여 상황에 맞게 처리하여야 한다.


호출 에러 처리

fd = open(pathname, flags, mode);

if(fd == -1){

perror("open");

exit(EXIT_FAILURE);

}


라이브러리 함수의 에러 처리는 3가지 정도로 분류가 된다!

  • 시스템 호출과 똑같은 에러 정보
  • errno에 구체적인 에러 번호
  • errno를 전혀 안씀,,,,, 함수 매뉴얼 확인하세요


3.5 이 책의 예제 프로그램 은 통과~~~



3.6 이식성 이슈


3.6.1 기능 테스트 매크로


시스템 호출과 라이브러리 함수 API의 동작에 대한 여러가지 표준이 존재한다. 이식성 있는 응용 프로그램을 작성할 때는 헤더 파일이 특정 표준을 따르는지를 나타내는 정의가 있으면 편리할 때가 있다. 이를 위해 프로그램을 컴파일할 때 기능 테스트 매크로를 정의한다.


사용법

#define _BSD_SORUCE 1 혹은 $cc -D_BSD_SOURCE prog.c


종류

_POSIX_SOURCE

_POSIX_C_SOURCE

_XOPEN_SOURCE

등이 존재


glibc 고유의 기능 테스트 매크로

_BSD_SOURCE

_SVID_SOURCE

_GNU_SOURCE


여러개의 매크로를 동시에 정의할 수 있다.


$ cc -D_POSIX_SOURCE -D_POSIX_C_SOURCE=199506 -D_BSD_SOURCE -D_SVID_SOURCE prog.c


3.6.2 시스템 데이터형


프로세스ID, 사용자 ID, 파일 오프셋 등 여러가지 구현 데이터형이 표준C 데이터형으로 표현되어있다. int나 long 같은 기초 데이터형을 사용하면 문제가 발생한다


유닉스마다 기초 데이터형의 크기가 다르다! (4바이트일수도, 8바이트 일수도)

같은 종류의 유닉스라도 버전에 따라 다를수 있다


이런 이식성 문제 때문에 표준 시스템 데이터형을 명시하기 시작하였다.

리눅스/x86-32에서는 아래와 같이 정의되어 있고

typedef int pid_t;

사용은

pid_t mypid;


시스템 데이터형 값을 표시할 때에는 printf()를 호출할 때 숫자의 표현에 따라 출력 결과가 달라지지 않도록 주의해야한다. 

따라서 %ld를 쓰고 언제나 해당 값을 long으로 캐스팅하여 사용한다.


pid_t mypid;

mypid = getpid();

printf("My PID is %ld\n", (long) mypid);



3.6.3 기타 이식성 이슈


구조체의 초기화와 사용


각 유닉스 구현에는 여러가지 시스템 호출과 라이브러리 함수에서 쓰이는 표준 구조체가 정의되어 있다.


semop() 의 sembuf 구조체를 봅시다.


struct sembuf{

unsigned short    sem_num;

short                sem_op;

short                sem_flg;

}


필드의 순서가 없고, 새로운 필드가 추가될 수도 있다.


따라서 


struct sembuf s = { 3, -1, SEM_UNDO};


와 같은 구조체 초기화는 이식성이 없다.


그래서


struct sembuf s;


s.sem_num = 3;

s.sem_op = -1;

s.sem_flg = SEM_UNDO;    


c99에서는


struct sembuf s = { .sem_num = 3, .sem_op = -1, .sem_flg = SEM_UNDO}; 


으로도 사용한다.


구현에 따라 존재하지 않을 수도 있는 매크로는


#ifdef WCOREDUMP

WCOREDUMP() 매크로 사용

#endif


를 사용하여 처리한다.



3.7 정리


프로세스는 시스템 호출을 통해 커널에게 서비스를 요청할 수 있다. 시스템 호출을 실행하기 위해서는 시스템이 잠깐 커널 모드로 전환해야 하고, 시스템 호출의 인자를 검증한 뒤 데이터를 사용자 메모리에서 전달해야 하기 때문에

아주 간단한 시스템이라도, 사용자 공간 함수 호출에 비해 커다란 오버헤드가 있다.


표준C라이브러리는 광범위한 작업을 수행하는 라이브러리 함수를 많이 제공한다.

리눅스에서 흔히 쓰이는 표준 C 라이브러리 구현은 glibc다.


대부분의 시스템 호출과 라이브러리 함수는 호출이 성공했는지, 실패했는지를 알리는 상태값을 리턴하고, 항상 확인해야한다.


표준을 준수하는 어떤 시스템에서도 동작하는 이식성 있는 시스템 프로그램을 작성하기 위한 가이드라인.


응용 프로그램을 컴파일할 때, 헤더 파일에 들어 있는 정의 중 어느것을 노출시킬지를 제어하는 여러 가지 기능 테스트 매크로를 정의할 수 있다.


C의 기본 데이터형보다는 여러가지 표준에 정의된 시스템 데이터형을 사용함으로서 이식성을 높일 수 있다.




'리눅스' 카테고리의 다른 글

gTile - ubuntu 18.04 화면 분할(Mac - magnet 같은)  (0) 2019.12.09
Remmina 파일 공유 방법  (0) 2019.07.08
4장 파일 I/O: 범용 I/O 모델  (0) 2018.05.16
기초 리눅스 API  (2) 2018.01.16