시스템 콜 system call ?
리눅스의 세 가지 개념 중 '스트림(stream)'이 있었다. 이 스트림과 관련된 것은 시스템 콜과 라이브러리 함수이다. 라이브러리보다 시스템 콜이 커널 쪽에 좀 더 가깝다. 라이브러리는 유저모드에서 동작하며 시스템 콜은 커널(운영체제)에서 동작한다. 참고로 시스템 콜은, 그야말로 리눅스 프로그램에서 핵심이다.
>> 함께 보기 좋은 포스팅
https://jinn-o.tistory.com/119?category=971524
https://jinn-o.tistory.com/120?category=971524
https://jinn-o.tistory.com/124
리눅스(유닉스)의 입출력은 대부분 다음 네 개의 시스템 콜로 처리된다.
read | 스트림에서 바이트 열을 읽는다. |
write | 스트림에 바이트 열을 쓴다. |
open | 새로운 스트림을 생성한다. |
close | 사용 완료한 스트림을 닫는다. |
read(2)
스트림에서 바이트 열을 읽기 위해 사용하는 시스템 콜이다.
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t butsize);
내용 | 설명 |
read(2) 에서 2 | 시스템 콜을 의미한다. (man 명령어에서 2는 시스템 콜을 의미한다.) |
#include <unistd.h> | API 를 사용하기 위해서 해당 헤더 파일을 포함해야 한다. 즉, read(2) 를 사용하기 위해서는 unistd.h를 include 해야 한다. |
ssize_t 그리고 size_t | sys/types.h 에 정의된 자료형으로 정수형의 별명(alias)이다. 말하자면 int 타입 또는 log 타입이다. ssize_t는 부호가 있는 정수형이고, size_t는 부호가 없는 정수형이다. |
fd | 파일 디스크립터 번호 |
*buf | 메모리 공간 |
bufsize | buffer 의 최대 사이즈 |
read()는 파일 디스크립터 번호인 fd에 해당하는 스트림에서 바이트 열을 읽는 시스템 콜이다. 읽기 작업이 순조롭게 완료되면 읽어 들인 바이트 수를 반환한다. 그리고 파일의 끝에 도달한 경우에는 0을 반환하고, 중간에 에러가 발생한 경우에는 -1을 반환한다. bufsize 바이트 수보다 적은 바이트 수를 읽는 경우도 많으므로 반환값을 체크하도록 코딩해야 한다.
그런데 C 언어 문자열(char 배열)에는 임의의 바이트 열을 저장할 수 있지만, 일반적으로 문자 열의 끝에는 '\0'을 넣는 게 관례다. API 중에도 문자열의 끝에 '\0'이 있다고 전제하는 것과 그렇지 않은 것이 있어 사용에 주의해야 한다. read(2)의 경우는 읽어 들인 데이터의 끝에 '\0'가 있다고 전제하지 않는 API다. 따라서 read(2)를 통해 읽어 들인 문자열의 끝에 '\0'이 있다고 생각하고 코드를 작성해서는 안된다. 예를 들어, printf()의 경우는 문자열의 끝에 '\0'이 들어가 있다고 전제하는 API이므로 read(2)로 읽은 문자열을 그대로 printf()로 전달해서는 안된다. 이는 보안상 취약점이 될 수도 있다.
>> read() 시스템 콜 직접 실습해보기
https://jinn-o.tistory.com/125
write(2)
스트림에 바이트 열을 쓸 때 사용하는 시스템 콜이다.
#include <unistd.h>
ssize_t write(int fd, const *bf, size_t bufsize);
write()는 인자로 지정한 bufsize 바이트만큼 buf의 내용을 fd로 지정한 파일 디스크립터의 스트림에 쓴다. 정상적으로 쓴 바이트 수를 반환하고 에러가 발생한 경우는 -1을 반환한다.
>> write() 시스템 콜 직접 실습해보기
https://jinn-o.tistory.com/126
open(2)
파일을 읽고 쓰는 스트림을 만들기 위한 시스템 콜이다.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 대괄호[]는 선택 인자를 의미한다.
int open(const char *pathname, int flags, [mode_t mode]);
내용 | 설명 |
const char *pathname | 절대 경로 or 상대 경로. 여기에 지정한 경로의 파일에 대한 스트림을 만들고, 그 스트림을 가리키는 파일 디스크립터를 반환한다. 이러한 과정을 흔히 파일을 연다(open)고 한다. |
int flags | 파일을 어떤 모드로 열 것인지 지정한다. |
mode_t mode | 두 번째 인자 flags에 O_CREAT를 설정했을 때만 유효한 인자다. 새로운 파일을 만들 때, 그 파일의 권한을 설정한다. (S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP ... S_IROTH, ... 등등) |
반환 값 | 성공한다면 파일 디스크립터를 반환하고, 실패한다면 -1 을 반환한다. |
int flags
이때 1은 man 명령어에서 실행 가능한 프로그램이나 셸 명령어를 의미한다.
flag(1)의 종류 | 의미 |
O_RDONLY | 읽기 전용 |
O_WRONLY | 쓰기 전용 |
O_RDWR | 읽고 쓰기 |
만약 O_WRONLY 또는 O_RDWR 을 사용하여 쓰는 스트림을 만들 때는 아래와 같은 추가적인 옵션을 지정할 수 있다.
flag(2)의 종류 | 의미 |
O_CREAT | 파일이 존재하지 않으면 새로 만든다. |
O_EXCL | Exclusive(배타적)라는 의미이며, O_CREAT와 함께 사용되어 이미 파일이 존재하면 에러가 된다. |
O_TRUNC | Truncate(잘라 없애다)라는 의미이며, O_CREAT와 함께 사용되어 이미 파일이 존재하면 파일의 크기를 0으로 만든다. 즉, 기존에 작성되어 있는 데이터를 날리고 새로 작성한다. 덮어쓰기라고 생각하면 쉽다. |
O_APPEND | 기존의 데이터 뒤에 작성한 내용을 추가한다. |
O_NONBLOCK | 만약 BLOCK의 상태라면 데이터가 들어올 때까지 프로그램이 블락킹 상태가 되는데, 이때 블락킹 상태란 프로그램이 멈춘다고 생각하면 된다. 그러나 NONBLOCK 상태라면 데이터가 있든 없든 기다리지 않고 나온다. (백그라운드 작업과 비슷하다) |
O_SYNC | Synchronous라는 의미이며, 작업 내용이 항상 디스크에 반영되도록 한다. |
close(2)
사용이 끝난 스트림을 닫는 시스템 콜이다.
#include <unistd.h>
int close(int fd);
close()는 파일 디스크립터 fd에 연결된 스트림을 해제한다. 오류 없이 닫히면 0, 에러가 발생하면 -1을 반환한다. 일반적으로 close()함수를 호출하는 코드는 다음과 같다.
if (close(fd) < 0) {
// ...
}
프로세스가 종료되면 사용하던 모든 스트림을 커널이 파기하기 때문에 close()를 하지 않아도 시스템에 이상이 생기지 않을 수도 있다. 그러나 사용이 완료된 스트림은 반드시 바로 종료해주는 것이 좋다. 프로세스가 동시에 사용할 수 있는 스트림의 개수에 제한이 있기도 하고, 스트림의 반대편에 프로세스가 close()할 때까지 기다리고 있을 수도 있다. 모든 리소스는 사용이 완료되었을 때 닫아주는 것이 바람직하다.
'컴퓨터 공학 > 시스템 프로그래밍' 카테고리의 다른 글
[리눅스 프로그래밍] 스트림 stream (0) | 2021.10.10 |
---|---|
[리눅스 프로그래밍] 파일 디스크립터 File Discriptor (0) | 2021.10.10 |
[리눅스 프로그래밍] 리눅스의 다중 사용자 시스템, 권한 설정 (0) | 2021.10.10 |
[리눅스 프로그래밍] 리눅스의 세 가지 중요 개념 : 파일시스템, 프로세스, 스트림 (0) | 2021.10.09 |
[리눅스 프로그래밍] 리눅스 추상화의 층구조 (0) | 2021.10.09 |