|
System Program/I/O 2013. 10. 10. 07:28
1. 입출력 관련 시스템 호출
I/O를 수행하는 모든 시스템 호출은 파일 디스크립터라는 음이 아닌 정수를 통해 열려 있는 파일을 참조한다.
파일 디스크립터는 파이프, FIFO, 소켓, 터미널, 디바이스, 일반 파일 등 종류에 상관없이 모든 열려 있는 파일을 참조한다.
유닉스/리눅스의 I/O처리 철학이 모든 입출력 장치를 파일로 관주하는 맥락과 일치한다.
2. 표준 파일 디스트립터
파일 디스트립터 |
목적 |
POSIX 이름 |
STDIO STREAM |
0 |
표준 입력 |
STDIN_FILENO |
stdin |
1 |
표준 출력 |
STDOUT_FILENO |
stdout |
2 |
표준 에러 |
STDERR_FILENO |
stderr |
위의 세 디스트립터는 항상 열려 있는데, 이유는 쉘을 통해 실행되는 프로그램은 쉘로부터 항상 열려있는 세개의 디스트립터를 상속받기 때문이다. 이 개념은 매우 중요하다. 네트워크나 이와 유사한 프로그램에서 dup()함수를 통해서 열려 있는 소켓의 디스크립터의 번호를 재 정의할 때 이 개념이 정립되어 있어야지 이해할 수 있다.
또한 프로그램에서 파일 디스트립터의 숫자를 통해서 파일을 처리할 수 있으나, 가능하면 <unistd.h>에 정의된 POSIX 표준 이름을 쓰는 편이 좋다.
2.1. 파일 열기: open()
#incldue <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, ... /* mode_t mode */);
성공하면 파일 디스크립터를 리턴하고, 에러가 발생하면 -1을 리턴한다.
open()함수가 리턴하는 파일 디스크립터는 항상 프로세스에서 사용하지 않는 가장 작은 수를 리턴한다. 따라서 기본적으로 앞에서 설명했듯이 0 ~ 2는 사용하고 있으므로 상식적으로 3부터 차례로 리턴될 것이다.
이런 동작 방식에 대한 이해를 하는 것이 매우 중요하다.
open()함수에서 사용하는 flags에 대해서 아래와 같이 정리할 수 있다.
플래그 |
목적 |
표준 |
O_RDONLY |
읽기 전용으로 연다 |
v3 |
OWRONLY |
쓰기 전용으로 연다 |
v3 |
O_RDWR |
읽고 쓰기용으로 연다 |
v3 |
O_CLOEXEC |
실행 시 닫기 플래그를 설정한다 |
v4 |
O_CREAT |
파일이 이미 존재하면 새로 만든다 |
v3 |
O_DIRECT |
파일 I/O가 버퍼 캐시를 우회한다 |
|
O_DIRECTORY |
pathname이 디렉토리가 아니면 실패한다 |
v4 |
O_EXCL |
O_CREAT와 함께, 배타적으로 파일을 만든다 |
v3 |
O_LARGEFILE |
32비트 시스템에서 큰 파일을 열 때 쓴다 |
|
O_NOATIME |
read()시에 파일 최종 접근 시간을 갱신하지 않는다 |
|
O_NOCTTY |
pathname으로 제어 터미널이 되지 않게 한다 |
v3 |
O_NOFOLLOW |
심볼링크를 역참조하지 않는다 |
v4 |
O_TRUNC |
기존 파일의 길이를 0으로 설정한다 |
v3 |
O_APPEND |
언제나 파일 끝에 추가해서 쓴다 |
v3 |
O_ASYNC |
I/O가 가능해지면 시그널을 보낸다 |
|
O_DSYNC |
동기 I/O 데이터 무결성을 제공한다 |
리눅스 : 2.6.33부터 |
O_NONBLOCK |
비블로킹 모드로 연다 |
v3 |
O_SYNC |
파일 쓰기를 동기 모드로 설정한다 |
v3 |
■ open() error 값
에러가 발생하면 '-1'을 리턴하고, errno를 보면 에러의 원인을 알 수 있다. 발생 가능한 에러에 대해서 살펴본다.
EACCES |
호출하는 프로세스가 flags에 지정한 모드로 파일에 접근할 수 없다 |
EISDIR |
지정된 파일이 디렉토리이고, 호출자가 쓰기용으로 열려고 했다 |
EMFILE |
파일디스크립터의 개수가 프로세스의 한도를 넘었다 |
ENFILE |
열린 파일의 개수가 시스템 전체 한도에 도달했다 |
ENOENT |
지정된 파일이 존재하지 않고, O_CREAT가 지정되지 않았거나, 지정되었더라도 pathname으로 지정된 경로명이 존재하지 않는다 |
EROFS |
파일이 읽기전용인데 호출자 쓰기용으로 열려고 했다 |
ETXBSY |
지정된 파일이 실행 중에 있다. |
2.2. create() 시스템 호출
#include <fcntl.h>
int create(const char *pathname, mode_t mode );
성공하면 파일 디스크립터를 리턴하고, 에러가 발생하면 -1을 리턴한다.
open()과 동일한 기능이지만 현재는 사용되지 않는다.
2.3. read() 시스템 호출
#include <unistd.h>
ssize_t read(int fd, void *buffer, size_t count);
읽은 바이트 수를 리턴한다. EOF인 경우에는 0을 에러가 발생하면 -1을 리턴
위의 함수에서 size_t count는 매개변수는 읽을 최대 바이트 수를 지정한다.
2.4. write() 시스템 호출
#include <unistd.h>
ssize_t write(int fd, void *buffer, size_t count);
쓴 바이트 수를 리턴한다. 에러가 발생하면 -1을 리턴
디스크에서 I/O를 수행할 때 write()가 성공하였다고해서 데이터가 디스크로 전송되었다고 보증할 수는 없다. 커널의 디스크 I/O 동작에 대해서 버퍼링을 수행하기 때문이다. 자세한 내용은 차 후에 보도록 한다.
2.5. close() 시스템 호출
#include <unistd.h>
int close(int fd);
성공하면 0을 에러가 발생하면 -1을 리턴
2.6. lseek() 시스템 호출
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
성공하면 새로운 파일 오프셋을 리턴하고, 에러가 발생하면 -1을 리턴
위에서 offset은 바이트 단위로 설정하여 파일 시작에서 몇 바이트 떨어져 있는 지점을 지정하기 위해서 사용된다.
뒤의 whence는 아래와 같은 의미를 갖는다. 이 함수는 모든 종류의 디바이스에 적용할 수 있는 것은 아니다. 일반적으로
파일에 적용될 수 있다고 보면 된다.
- SEEK_SET : 파일 오프셋 계산의 기준점이 파일의 시작점으로 설정
- SEEK_CUR : 파일 오프셋의 기준점을 현재 파일 오프셋으로 설정
- SEEK_END : 파일 오프셋의 기준점을 파일 끝으로 설정
2.7. ioctl() 시스템 호출
#include <sys/ioctl.h>
int ioctl(int fd, int request, ... /*argp */);
성공하면 request에 따라 다르다, 에러가 발생하면 -1을 리턴
위의 사용은 일반적으로 디바이스 드라이버 응용에서 많이 사용하는 방식이다. 커널쪽에 ioctl의 request에 대응하는 코드가 별도로 구현되야 한다.
즉 위의 함수는 앞서 설명한 범용 I/O 모델에서 벗어나는 파일과 디바이스 오퍼레이션을 위한 범용 메카리즘이다.
3. 기타
커널 2.6.22부터는 /proc/PID/fdinfo 디렉토리에서 각 프로세스에 대한 파일 디스크립터 정보를 얻을 수 있다.
자세한 내용은 참고서적 144Page 참조
System Program/common 2013. 8. 8. 07:22
1. 사용자 관리 시스템
1.1. /etc/passwd
7개의 필드로 구성된 한줄의 기록으로 사용자를 관리한다. 각 필드는 콜론 (:)으로 구분된다.
[로그인이름]:[암화화된 패스워드]:[사용자ID]:[그룹ID]:[주석]:[홈디렉토리]:[로그인쉘]
각 필드의 구성은 다음과 같다.
- 로그인 이름 : 사용자가 로그인할 시 입력해야 하는 고유 이름.
- 암호화된 패스워드 : 13자로 이뤄진 암호화된 패스워드. 최근에는 새도 패스워드를 사용하여 별도의 시스템 파일에 관리하고 있다. 이 경우 보통 필드는 'x'로 표시되어 있다. 만약 해당 필드가 비어 있으면 패스워드 없이 로그인이 가능하다.
- 사용자 ID : 이 필드의 값이 '0'이면 슈퍼유저 권한을 갖는다. 커널 2.4 이후에는 32비트로 저장된다.
- 그룹 ID: 사용자가 속하는 첫 번째 그룹의 숫자 ID. 사용자가 속하는 나머지 그룹은 시스템 그룹 파일에 존재
- 홈디렉토리 : $HOME 환경변수의 값이 된다.
- 로그인 쉘 : 사용자가 로그인한 다음 제어권을 넘겨받는 프로그램. 보통은 쉘이지만 어떤 프로그램이어도 상관 없다. 이 필드는 $SHELL 환경 변수의 값이 된다.
[예시]
hskim@hskim-VirtualBox:~$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/shuucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh syslog:x:101:103::/home/syslog:/bin/false messagebus:x:102:105::/var/run/dbus:/bin/false colord:x:103:108:colord colour management daemon,,,:/var/lib/colord:/bin/false lightdm:x:104:111:Light Display Manager:/var/lib/lightdm:/bin/false whoopsie:x:105:114::/nonexistent:/bin/false avahi-autoipd:x:106:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false avahi:x:107:118:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false usbmux:x:108:46:usbmux daemon,,,:/home/usbmux:/bin/false kernoops:x:109:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false pulse:x:110:119:PulseAudio daemon,,,:/var/run/pulse:/bin/false rtkit:x:111:122:RealtimeKit,,,:/proc:/bin/false speech-dispatcher:x:112:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh hplip:x:113:7:HPLIP system user,,,:/var/run/hplip:/bin/false saned:x:114:123::/home/saned:/bin/false hskim:x:1000:1000:hskim,,,:/home/hskim:/bin/bash sshd:x:115:65534::/var/run/sshd:/usr/sbin/nologin vboxadd:x:999:1::/var/run/vboxadd:/bin/false
1.2. /etc/shadow (새도 패스워드)
과거에 유닉스 시스템은 암호화된 패스워드를 포함한 모든 사용자 정보를 /etc/passwd 파일에서 관리하였다. 하지만 이는 보안상의 문제가 있었다. 따라서 보안상에 민감하지 않는 모든 사용자 정보는 공개적으로 읽을 수 있는 패스워드 파일에 두고 암호화되는 패스워드는 특권 프로그램만 읽을 수 있는 새도 패스워드 파일에 두었다.
새도 패스워드 파일은 모든 시스템에서 기능을 제공하는 것은 아니며 자세한 사항은 shadow(5) 메뉴얼 페이지 참조
2. 그룹 관리
여러 관리상의 이유로, 특히 파일과 기타 시스템 자원에 대한 접근 권한을 제어할 떄, 사용자들을 그룹으로 묶으면 편리하다. 하지만 위에서 사용자 관리 시스템을 설명하면서 사용자 관리 파일에서도 그룹을 관리하는 필드가 존재한다. 사용자 시스템에서 관리하는데 왜 또 별도의 파일이 추가된 이유는 역사적인 이유라고 한다.
즉 과거에는 사용자는 한 그룹에만 속했는데 4.2BSD 표준에서 시작하여 현재 POSIX.1-1990에서 표준화되어 한 사용자는 여러 그룹에 속할 수 있는 역사적 배경에 근거한다.
2.1. /etc/group
4개의 필드로 구성되어 있으면 구성은 아래와 같다.
[그룹이름]:[암호화된 패스워드]:[그룹 ID]:[사용자목록]
- 그룹이름 : 패스워드의 로그인 이름처럼 사람이 읽을 수 있는 ID이다.
- 암호화된 패스워드 : 그룹 패스워드 (선택사항). 현재는 거의 사용하지 않음. 패스워드가 설정되어 있으면 newgrp(1)을 실행하면 패스워드를 문의하는데 이 때 비교되는 패스워드이다.
- 사용자 목록: 해당 그룹에 속한 사용자를 콤마(,) 구분하여 나열한다.
3. 참고 사항
아래의 사항을 보자. 사용자 avr 이 세개의 그룹 (users, staff, teach)에 속한다고 해 보자. 시스템 관리 파일은 다음과 같이 구성될 수 있을 것이다.
1. /etc/passwd 파일 구성 avr:x:100:100:Anthony Robins:/home/avr:/bin/bash
2. /etc/group 파일 구성
users:x:100:
staff:x:101:mtk,avr,martini
teach:x:104:avr,rlb,alc
위에서 사용자 패스워드 관리 시스템 파일에서 avr의 그룹 ID는 100이다. 하지만 아래 그룹 관리 시스템 파일에서는 각각 100, 101,104에 포함되어 있다.
정리하면 사용자 패스워드 시스템 파일에 대표 그룹 ID를 표시하고, 나머지 그룹에 대한 것은 해당 그룹 파일 시스템의 사용자 목록 필드에 사용자를 적어준다는 것을 알 수 있다.
System Program/common 2013. 8. 7. 13:00
1. 리눅스 마운트 정보 관리
리눅스 시스템이 처음 기동할 때 마운트 할 정보를 가지고 있는 파일이다.
리눅스 부팅 후에 리눅스에 현재 마운트되어 있는 정보를 관리하는 파일이다.
참고로 UUID관련 내용은 다음 웹페이지를 참조한다.
http://blog.simplism.kr/?p=2578
2. mount 명령어
mount [-fnrvw] [-t 파일시스템 종류] [-o 옵션] 디바이스명 디렉터리명
2.1. [ -fnrvw ] option
옵션 |
설명 |
v |
자세한 내용을 출력합니다. |
f |
실제로 마운트를 하지 않고 마운트를 할 수 있는지 점검합니다 |
n |
실제로 마운트를 하지 않고 마운트를 할 수 있는지 점검합니다 |
r |
읽기만 가능하게 마운트 합니다. -o ro 옵션과 같습니다. |
w |
읽기/쓰기 모드로 마운트 합니다. 기본설정값이며 -o rw 옵션과 같습니다. |
2.2. [-t 파일 시스템 종류] option
옵션 |
설명 |
t |
파일시스템 종류에는 ext3, iso9660등 파일시스템 종류를 적어줍니다.
만약 이 옵션을 사용하지 않는다면 파일시스템의 수퍼블럭을 조사하여
자동으로 파일시스템이 선택됩니다. |
2.3. [-o ] option
옵션 |
설명 |
defaults |
rw, suid, dev, auto, nouser, async의 옵션이 선택됩니다. |
async |
파일시스템에 대한 입출력이 비동기적으로 이루어지게 합니다. |
auto |
-a 옵션으로 마운트가 가능하게 합니다. |
dev |
파일시스템의 문자, 블럭 특수장치를 해석합니다. |
exec |
바이너리의 실행을 허가 합니다. |
noauto |
-a 옵션으로는 마운트 되지 않으며 명시적으로만 마운트가 가능합니다. |
noexec |
바이너리의 실행을 허가하지 않습니다. |
nosuid |
set-UID, set-GID를 무시합니다. |
nouser |
일반 사용자가 마운트할 수 없게 합니다. |
remount |
이미 마운트된 파일시스템을 다시 마운트 합니다. |
ro |
읽기 전용 |
rw |
읽기 쓰기 가능 |
suid |
nosuid 반대 기능 |
sync |
파일 시스템에 대한 입출력 동기화 |
user |
nouser 반대 기능 |
3. unmount 명령어
umount [-nv] 디바이스명 or 디렉터리명
3.1. [-nvat ] option
옵션 |
설명 |
n |
/etc/mtab 파일을 갱신하지 않고 마운트를 해제 합니다. |
v |
자세한 정보를 보여줍니다. |
a |
/etc/mtab 파일에 명시된 모든 파일시스템의 마운트를 해제 합니다. |
t |
-a 명령어와 함께 사용되며 이 옵션이 지정되면 /etc/mtab 파일에 명시된 모든 파일시스템 중 유저가 입력한 파일시스템만 마운트를 해제 |
4. mount -a [-fnrvw] [-t fiilesystem]
/etc/fstab에 기술된 디바이스 노드들을 자동으로 마운트하는 명령어
일반적으로 초기 부팅 쉘에 사용된다.
System Program 2013. 8. 5. 13:08
1. 시스템 호출이란
커널로의 진입점이며, 이를 통해서 프로세서가 커널에게 프로세스 대신 어떤 동작을 수행하도록 요청할 수 있다.
커널은 API를 통해서 광범위한 서비스를 제공한다.
1.1 시스템 호출의 일반적인 사항
- 시스템 호출은 프로세스의 상태를 사용자모드에서 커널모드로 변경해서 CPU가 보호된 커널 메모리에 접근할 수 있다.
- 시스템 호출 목록은 고정되어 있다. 각 시스템 호출에는 고유한 숫자가 붙여 있다.
- 표준 C 라이브러는 시스템 호출함수를 Wapping하고 있는 함수를 제공한다.
(예로, fopen --> open --> sys_open 순)
2. 표준 C 라이브러리 : GNU C 라이브러리(glib)
유닉스는 구현에 따라서 표준 C 라이브러리의 구현이 다를 수 있다. 리눅스에서는 가장 널리 쓰이는 구현은 GNU 라이브러리이다.
2.1. 참조 사이트
- glib, http://www.gnu.org/software/libc/
2.2. glibc 버전 및 위치 확인
2.2.1. 쉘상에서 확인 방법
[shell 상에서 확인 내용]
/lib # ./libc.so.6 GNU C Library stable release version 2.9, by Roland McGrath et al. Copyright (C) 2008 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 4.3.2. Compiled on a Linux >>2.6.17-10-generic<< system on 2009-07-24. Available extensions: crypt add-on version 2.1 by Michael Glad and others Native POSIX Threads Library by Ulrich Drepper et al Support for some architectures added on, not maintained in glibc core. BIND-8.2.3-T5B For bug reporting instructions, please see: <http://www.gnu.org/software/libc/bugs.html>.
2.2.2. 라이브러리 위치가 lib 폴더에 있지 않을 경우 확인 방법
라이브러리는 항상
- $ ldd mypro | grep libc
소스상에서는 매크로 정의나 버전정보를 리턴하는 함수를 사용할 수 있다.
- __GLIBC__ and __GLIBC_MINOR__
- const char *gnu_get_libc_version(void) --> (#include <gnu/libc-version.h> 필요)
3. 시스템 호출과 라이브러리 함수의 에러처리
거의 대부분의 시스템 호출과 라이브러리 함수가 어떤 형태로든 성공여부를 나타내는 상태 값을 리턴 한다.
3.1. 시스템 호출 에러 처리
시스템 호출의 매뉴얼 페이지에는 가능한 리턴값들이 나와 있다. (vi editor에서 shift + k로 확인가능) .
일반적으로 시스템 호출이 성공하면 '0'의 값을 리턴하는 것이 통상적인 동작이다.
시스템 호출이 실패하면 전역 정수 변수 errno를 특정 에러를 나타내는 양수로 설정한다. 헤더 파일 <errno.h>를 사용하면
errno 선언 뿐만 아니라 다양한 에러 상수도 확인할 수 있다.
3.2. 시스템 호출의 에러 출력
시스템 호출이 실패한 후의 일반적인 동작은 errno값에 따라 에러 메시지를 출력하는 것이다. 아래와 같은 라이브러리 함수를 사용하면 된다.
#include <errno.h>
void perror(const char *msg)
에러 number에 해당하는 에러 문자열을 출력한다.
#include <string.h>
void *strerror(int errnum)
[사용 예시]
fd = open(pathname, flag, mode)
if(fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
3.3 라이브러리 함수의 에러 처리
다양한 라이브러리 함수가 실패를 알리기 위해서 여러 가지 데이터형의 여러 가지 값을 리턴한다.
따라서 일반적인 에러 처리 방식을 아래와 같이 분류할 수 있다.
- 시스템 호출과 똑같이 에러 정보를 리턴하는 라이브러리 함수. '-1'을 리턴하고 구체적인 에러 값을 errno에 적는다.
- 에러 발생 시 '-1'을 리턴하지는 않지만 errno에 구체적인 에러 값을 적는다.
- errno을 전혀 쓰지 않는 나머지 라이브리 함수. 이 경우 perror()나 strerror()함수를 사용하여 에러를 진단해서는 안된다.
정리하면 정확한 에러 처리를 위해서는 매뉴얼을 확인하는 것이 최선의 방법인 것 같다.
System Program/terminal 2013. 7. 20. 14:02
1. 가상터미널
가상터미널(pseudo terminal)은 네트워크를 통해서 호스트에 접속을 시도하는 원격로그인 프로그램에서 사용하는 일종의 가상 터미널이다. 원격지에서 터미널 접속을 시도하는 방식은 기존 tty에서 처럼 serial line을 통해서 직접적으로 접속하는 방식과는 차이가 있다. 따라서 이런 차이를 내부적으로 숨기면서 직접 터미널에 접속한 것 같은 기능을 제공하기 위해서 탄생한 터미널이 가상터미널 방식이다.
2. 가상터미널 구성
pseudo-device들의 쌍으로 존재하며, 구성요소는 기능에 따라 master와 slaver로 구분된다.
master의 역활은 터미널 emulator가 slave를 제어할 수 있는 방법을 제공하고, slave는 tty의 디바이스처럼 실제 터미널의 디바이스를 의미한다. tty의 디바이스가 /dev/ttyn 의 형태로 존재하였다면, 가상터미널의 slave는 /dev/pts의 변종의 형태로 존재한다. 각각의 Linux 들에 대해서 아래에서 알아본다.
3. BSD PTY
slave device 파일은 /dev/tty[p-za-e][0-9a-f]의 형태를 띈다. master device 파일은 /dev/pty[p-za-e][0-9a-f]의 형태를 띈다.
4. Unix98 PTY
master device : /dev/ptmx (pseudo-terminal master multiplexer)
master device를 open하면 /dev/pts/n 의 형태의 slave device가 생성된다.
5. Virtual Console
리눅스에서는 가상터미널의 화면을 가르키는 디바이스를 제공한다. /dev/vcsn과 /dev/vcsan의 형태로 존재한다.
해당 파일 디스크립터를 open하여 읽거나 쓰게되면 Text Mode의 가상터미널에 해당 내용이 출력하게 된다.
참고자료는 http://linux.about.com/library/cmd/blcmdl4_vcs.htm
6. Reference
http://en.wikipedia.org/wiki/Pseudo_terminal
System Program/terminal 2013. 7. 17. 09:27
1. 터미널이란?
다른 사람은 잘 모르겠는데, 인터넷이나 다른 서적에서 터미널에 대한 정의는 다양하다.
하지만 그 내용만으로는 잘 정리가 되지 않는다. 개인적으로 나는 아래와 같이 정리하고 싶다.
터미널을 크게 로컬연결과 원격연결로 구분하고 싶다. 로컬 연결은 실제적으로 RS232C나 USB등으로
메인 프레임 역활을 수행하는 장치에 위의 장치를 연결하여 입출력이 가능하도록 기능을 제공하는 것을
터미널이라고 정리하고 싶다.
원격의 경우에는 로컬과 달리 네트워크 스택이 추가적으로 포함되야하며 또한 터미널과 같은 입출력
기능을 제공해야 한다. 이런 경우는 기존 터미널 개념으로 접근하기 힘들며 이 때 나온 것이
가상터미널이라는 개념이다. 정리하면 원래 터미널 기능에 다른 추가적인 기능을 추가한 것을 '
가상터미널로 구분하고 기존 터미널 개념과 구분하고 싶다.
2. tty (teletype)
시스템(유닉스/리눅스)상에서 /dev/ttyn 의 문자 디바이스가 있다.
이 문자 디바이스는 터미널에 입출력 기능을 제공하는 커널단의 모듈이다.
따라서 터미널 기능을 담당하는 프로그램은 위의 문자 디바이스를 열어서 구현했다.
시간이 지나면서 입출력 장치가 다양해지면서 RS232C나 USB등도 지원하면서 문자 디바이스는
/dev/ttyS(x), /dev/ttyUSBx 형태의 문자디바이스도 추가되었다.
3. 터미널 드라이버
커널의 디바이스 드라이버의 배경 지식이 필요하기는 하나 위의 디바이스를 실제
응용단에서 제어하고 입출력을 하기위해서는 디바이스 드라이버를 통해서 수행한다.
각 디바이스 드라이버는 기본적으로 제공된다고 보고 이런 디바이스 드라이버를 제어하기 위한
표준화된 라이블러리가 있다. 여기서는 그런 라이블러리 사용법에 대해서 알아본다.
4. 터미널 입력 모드
터미널은 다양한 오퍼레이션을 제공하는데, 오퍼레이션이 입력 모드에 따라서 다르게 동작한다.
입력 모드와 입력 모드의 기본 오퍼레이션은 아래와 같다.
- 줄 단위로 터미널 입력을 처리하며, 줄 편집이 활성화되어 있다. 정규 모드는 기본 입력 모드이다.
- 비정규 모드 (Noncanonical Mode)
- 줄 단위 편집이 아닌 문자 단위 편집 기능을 제공한다.
터미널은 일반 문자 뿐만 아니라 다양한 특수 문자도 해석하며 특수 문자는 고유의 기능이 있다.
예로 CTRL+C는 터미널상에서 인터럽트로 해석된다.
5. 터미널 속성 읽기 및 수정을 위한 표준 라이블러리 함수
#include <termios.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_action, const struct termios *termios_p);
성공하면 '0'을 리턴, 에러가 발생하면 '-1'을 리턴
5.1. optional_action
- 변경 사항이 즉시 적용되도록 한다.
- 속성 변경 전에 모든 입출력 작업 완료하고 속성 변경을 적용한다 .
- TCSADRAIN와 유사하나 변경 시점의 입출력 대기중이 내용은 버린다.
5.2. 구조체
struct termios
{
tcflag_t c_iflag; /* 입력 플레그 */
tcflag_t c_oflag; /* 출력 플레그 */
tcflag_t c_cflag; /* 제어 플레그 */
tcflag_t c_lflag; /* 로컬 모드 */
cc_t c_line; /* 라인 규칙 */
cc_t c_cc[NCCS]; /* 터미널 특수 문자 */
speed_t c_ispeed; /* 입력 속도 (비표준, 미사용) */
speed_t c_ospeed; /* 출력 속도 (비표준, 미사용) */
};
6. 기타 정보
리눅스에서 /dev/console, /dev/tty 와 /dev/tty0의 차이점에 대해서 알아본다.
부팅 시에 실제 터미널로 사용되는 /dev/console이지만 /dev/ttyx에
추가적으로 콘솔과 같은 기능을 제공한다. 이를 가상 콘솔이라고 한다. 너무 용어가 많아서 햇깔린다.
- /dev/console : 부팅 시 사용하는 실제 터미널이다.
- /dev/tty[1-x] : 로그인 세션에서 터미널을 계속 열었을 때 추가되는 터미널
콘솔간의 이동은 ALT + (F1 ~ F6) 이고 XWindow 환경은 F7에 매핑되어 있다.
7. 예제
7.1. Enter키 입력없이 문자 입력과 동시에 옵션 값 출력하는 예제
입력 모드를 비 정규 모드로 전환하고 입력버퍼의 내용을 바로 출력버퍼로 복사하는
ECHO 기능을 비활성화 했다는 점이 포인트이다.
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
typedef enum {FALSE, TRUE} bool;
int getch(void)
{
int ch;
struct termios buf;
struct termios save;
tcgetattr(0, &save);
buf = save;
buf.c_lflag &= ~(ICANON|ECHO);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(0, TCSAFLUSH, &buf);
ch = getchar();
tcsetattr(0, TCSAFLUSH, &save);
return ch;
}
int main(int argc, char *argv[])
{
char ch;
bool exit;
printf("Insert Option \n");
do
{
ch = getch();
if(ch == 'q')
exit = FALSE;
printf("ch = %c \n", ch);
}
while(exit);
return 0;
}
7.2. 터미널의 윈도우 창 변경 시에 시그널이 발생함을 확인할 수 있는 예제이다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <termios.h>
#include <signal.h>
static int sighandler(int sig)
{
printf("%s\n", strsignal(sig));
}
int main(int argc, char *argv[])
{
struct sigaction sa;
struct winsize ws;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sighandler;
if(sigaction(SIGWINCH, &sa, NULL) == -1)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("tty name =%s \n", ttyname(STDIN_FILENO));
for(;;)
{
pause();
if(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
{
perror("sigaction");
exit(EXIT_FAILURE);
}
printf("SIGWINCH, new windown size : %d rows %d colums\n", ws.ws_row, ws.ws_col);
}
exit(EXIT_SUCCESS);
}
8. Reference
자세한 API관련 내용은 아래 링크 내용을 참조하면 많은 도움이된다.
http://www.joinc.co.kr/modules/moniwiki/wiki.php/article/termios
System Program/pthread 2013. 7. 12. 18:58
우선 순위 조정을 통하여 pthread 의 동작성을 확인해 본다
#include <stdio.h>
#include <string.h>
#include <pthread.h>
static int count = 0;
pthread_attr_t attr[2];
void *threadFunc1(void *arg)
{
char *s = (char *)arg;
struct sched_param sched;
while(1)
{
count++;
printf("%s [%d]\n", s, count);
pthread_attr_getschedparam(&attr[0], &sched);
printf("threadFunc1 priority = %d\n", sched.sched_priority);
//printf("pthread_self value = %d\n", pthread_self());
//usleep(100);
}
return (void *)strlen(s);
}
void *threadFunc2(void *arg)
{
char *s = (char *)arg;
struct sched_param sched;
while(1)
{
count--;
if(count < 0)
count = 0;
printf("%s [%d]\n", s, count);
pthread_attr_getschedparam(&attr[1], &sched);
printf("threadFunc2 priority = %d\n", sched.sched_priority);
//printf("pthread_self value = %d\n", pthread_self());
//usleep(100);
}
return (void *)strlen(s);
}
int main(int argc, char *argv[])
{
printf("pthread test start !!\n");
struct sched_param sched;
pthread_t tid[2];
void *res;
int retval;
pthread_attr_init(&attr[0]);
pthread_attr_init(&attr[1]);
pthread_attr_setinheritsched(&attr[0], PTHREAD_EXPLICIT_SCHED);
pthread_attr_setinheritsched(&attr[1], PTHREAD_EXPLICIT_SCHED);
printf("Message from main() \n");
pthread_attr_getschedparam(&attr[0], &sched);
printf("threadFunc1 priority = %d\n", sched.sched_priority);
sched.sched_priority = 1;
pthread_attr_setschedparam(&attr[0], &sched);
printf("threadFunc1 priority = %d\n", sched.sched_priority);
pthread_attr_getschedparam(&attr[1], &sched);
printf("threadFunc2 priority = %d\n", sched.sched_priority);
sched.sched_priority = 90;
pthread_attr_setschedparam(&attr[1], &sched);
printf("threadFunc2 priority = %d\n", sched.sched_priority);
retval = pthread_create(&tid[0], &attr[0], threadFunc1, "threadFunc1");
if(retval != 0)
{
printf("pthread_create error\n");
}
retval = pthread_create(&tid[1], &attr[1], threadFunc2, "threadFunc2");
if(retval != 0)
{
printf("pthread_create error\n");
}
retval = pthread_join(tid[0], &res);
if(retval != 0)
{
printf("pthread_join error\n");
}
retval = pthread_join(tid[1], &res);
if(retval != 0)
{
printf("pthread_join error\n");
}
printf("tid[0] = %d Thread returned %ld\n", tid[0], (long) res);
printf("tid[1] = %d Thread returned %ld\n", tid[1], (long) res);
return 0;
}
System Program/signal 2013. 7. 8. 13:07
1. sigaction()함수란?
signal()함수와 같이 시그널 속성을 설정하는 함수이다.
sigaction()은 시그널 핸들러를 만들 때, signal()보다는 복잡하지만 이식성이 좋은 장점이 있다.
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
성공하면 '0', 에러가 발생하면 '-1'을 리턴한다.
2. sigaction 구조체
sigaction()함수의 두 번째 매개변수로 정의는 아래와 같다.
struct sigaction {
void (*sa_handler) (int); 시그널 핸들러 void (*sa_sigaction) (int, siginfo_t *, void *); 시그널 핸들러로 위와 같다 sigset_t sa_mask; 시그널 핸들러가 동작 중 블록되는 시그널집합 int sa_flags; void (*sa_restorer) (void); 응용프로그램에서 사용용도가 아니다. }
여기서 sa_flag에 대해서 자세히 알아본다. sa_flag는 시그널이 어떻게 처리돼야 하는지를 제어한다.
여러 옵션은 아래와 같으며 OR 연산으로 같이 쓸 수 있다.
- sig가 SIGCHLD인 경우, 시그널을 추출한 결과로 자식프로세스가 멈추거나 재개될 때,
이 시그널을 생성하지 않는다.
- sig가 SIGCHLD인 경우에, 자식들이 제거될 때 좀비로 변하지 않는다.
- 이 시그널이 잡히면, 핸들러가 실행 중일때 프로세스 시그널 마스크에 자동적으로 추가하지 않는다.
- sigaltstack()에 설치된 대체 스택을 사용해 이 시그널에 대한 핸들러를 실행한다.
- 이 시그널이 잡히면, 핸들러를 실행하기 전에 기본 속성(SIG_DLF)으로 변경한다.
- 시그널 핸들러에 의해 중지된 시스템 호출을 자동적으로 재시작한다.
- 시그널에 대한 더 많은 정보를 제공하는 추가적인 인자로 시그널 핸들러를 실행한다.
3. 예제
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#define MAX_SLEEP_TIME 2
static int sigCnt[NSIG];
static volatile sig_atomic_t gotSigint = 0;
void printSigSet(FILE *of, const char *prefix, const sigset_t *sigset)
{
int sig, cnt;
cnt = 0;
for(sig = 1; sig < NSIG /*32*/; sig++)
{
if(sigismember(sigset, sig))
{
cnt++;
fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
}
}
if(cnt == 0)
fprintf(of, "%s<empty signal set >\n", prefix);
}
void signal_handler(int sig)
{
sigset_t pendingMask;
if(sig == SIGINT)
gotSigint = 1;
else
{
sigCnt[sig]++;
if(sigpending(&pendingMask) == -1)
fprintf(stderr, "sigpending\n");
printSigSet(stdout, "\t\t", &pendingMask);
sleep(MAX_SLEEP_TIME);
}
}
int main(int argc, char *argv[])
{
int i;
struct sigaction sigact;
//sigset_t pendingMask;
printf("%s: PID is %ld\n", argv[0], (long)getpid());
sigact.sa_handler = signal_handler;
sigfillset(&sigact.sa_mask);
//sigact.sa_flags = SA_RESETHAND | SA_RESTART;
sigact.sa_flags = SA_RESTART;
sigaction(SIGUSR2, &sigact, NULL);
sigaction(SIGINT, &sigact, NULL);
while(!gotSigint)
continue;
for(i = 1; i < NSIG; i++)
{
if(sigCnt[i] != 0)
printf("%s: signal %d caught %d time%s \n", argv[0], i, sigCnt[i], (sigCnt[i] == 1) ? "" : "s");
}
exit(EXIT_SUCCESS);
return 0;
}
4. 설명
앞 장에서 시그널을 전송하고 수신하는 예제에서 수신하는 프로그램을 수정하였다.
기존 앞 장은 signal()함수를 사용하였으나 여기서는 sigaction()함수로 대체하였다.
기존 앞 장에서는 signal()함수를 쓸 때는 여러가지 추가적인 시그널 처리 함수들을 호출하였다.
하지만 sigaction()함수는 그런 추가적인 함수 호출없이 동일한 기능을 할 수 있다.
System Program/signal 2013. 7. 8. 12:00
1. 시그널 전송 프로그램
시그널 SIGUSER2에 대해서 kill()시스템 함수를 통하여 1000000번을 설정한 PID 프로세스에 전송한다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#define MAX_SEND_SIGNUM 1000000
int main(int argc, char *argv[])
{
int i;
pid_t pid;
puts("insert PID");
scanf("%ld", &pid);
getchar(); // fflush(stdin);
printf("%s: sending signal %d to process %ld %d times\n", argv[0], SIGUSR2, (long)pid, MAX_SEND_SIGNUM);
for(i = 0; i < MAX_SEND_SIGNUM; i++)
if(kill(pid, SIGUSR2) == -1)
fprintf(stderr, "kill error [%s]\n", strerror(errno));
printf("%s: exiting\n", argv[0]);
exit(EXIT_SUCCESS);
return 0;
}
2. 시그널 수신 프로그램
위의 송신 프로그램에서 보낸 시그널을 수신하여 시그널별로 얼마나 처리가 되었는지 결과를 카운팅한다.
/*
* main.c -- C Test App.
*
* Copyright (C) 2012-2013, 2013 heesoon.kim <chipmaker.tistory.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#define MAX_SLEEP_TIME 25
static int sigCnt[NSIG];
static volatile sig_atomic_t gotSigint = 0;
void printSigSet(FILE *of, const char *prefix, const sigset_t *sigset)
{
int sig, cnt;
cnt = 0;
for(sig = 1; sig < NSIG /*32*/; sig++)
{
if(sigismember(sigset, sig))
{
cnt++;
fprintf(of, "%s%d (%s)\n", prefix, sig, strsignal(sig));
}
}
if(cnt == 0)
fprintf(of, "%s<empty signal set >\n", prefix);
}
void signal_handler(int sig)
{
if(sig == SIGINT)
gotSigint = 1;
else
sigCnt[sig]++;
}
int main(int argc, char *argv[])
{
int n;
sigset_t pendingMask, blockingMask, emptyMask;
printf("%s: PID is %ld\n", argv[0], (long)getpid());
for(n = 1; n < NSIG; n++)
(void) signal(n, signal_handler);
sigfillset(&blockingMask);
if(sigprocmask(SIG_SETMASK, &blockingMask, NULL) == -1)
fprintf(stderr, "sigprocmask\n");
printf("%s : sleeping for %d seconds\n", argv[0], MAX_SLEEP_TIME);
sleep(MAX_SLEEP_TIME);
if(sigpending(&pendingMask) == -1)
fprintf(stderr, "sigpending\n");
printf("%s : pending signals ars : \n", argv[0]);
printSigSet(stdout, "\t\t", &pendingMask);
sigemptyset(&emptyMask);
if(sigprocmask(SIG_SETMASK, &emptyMask, NULL) == -1)
fprintf(stderr, "sigprocmask\n");
while(!gotSigint)
continue;
for(n = 1; n < NSIG; n++)
if(sigCnt[n] != 0)
printf("%s: signal %d caught %d time%s \n", argv[0], n, sigCnt[n], (sigCnt[n] == 1) ? "" : "s");
exit(EXIT_SUCCESS);
return 0;
}
3. 설명
수신 프로그램은 초기 시그널 마스크를 sigfillset()함수를 통해서 모두 블록으로 초기화하고 25초간 수면상태로 진입 후
다시 모든 시그널을 블록하지 않는 구조이다.
송신 프로그램에서 보낸 시그널이 얼마나 처리되느냐는 위의 각 구간에서 얼마나 시그널이 수신되었느냐에 따라 다르다.
만약 초기 블록 후 수면상태까지 시그널이 모두 전송되었다면 단 하나의 시그널만 처리될 것이다. 왜냐하면 초기 블록된 상태에서 수신된 동일 시그널은 모두 보류 상태로 있고 보류 상태가 끝나면 단 하나의 시그널만 처리되기 때문이다.
하지만 만약 수면 상태 후에 시그널이 모두 '0'으로 초기화된 후에 전송된 시그널은 커널의 context switching을 고려하여
많은 수가 처리될 것이다.
4. 결론
시그널 방식은 비동기적인 방식으로 시그널이 프로세스에 제대로 전달되었는지 확인할 수 있는 방법이 없다.
또한 경우에 따라서는 처리가 되지 않고 버려지기 때문에 신호가 전달되었는지를 신뢰할 수 없으므로 비신뢰적인 특징을 가진다고 말할 수 있다.
System Program/signal 2013. 7. 8. 11:57
1. Kill 함수를 이용하여 시그널 보내기
signal을 전송하기 위한 가장 간단한 방법은 kill(2) 시스템함수를 이용하는 것으로, kill의 사용방법은 다음과 같다. - shell의 kill 명령과 혼동하지 말자. -
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig) ;
성공하면, '0'을 리턴, 에러가 발생하면 '-1'을 리턴
pid는 시그널을 받을 프로세스의 pid로 그룹 혹은 특정 pid에 시그널을 보낼 수 있다.
- pid > 0 : pid에 대응되는 프로세스에 시그널을 보낸다.
- pid == 0 : 현재프로세스에 속한 모든 그룹의 프로세스에 시그널을 보낸다.
- pid == -1 : 1번 프로세스 (init) 를 제외한 모든 프로세스에 시그널을 보낸다.
- pid < -1 : 프로세스 그룹에서 절대값으로 동일한 모든 프로세스에게 시그널을 보낸다.
어떤 프로세스도 명시된 pid와 일치하지 않으면 kill()은 실패하고 errno를 ESRCH(해당프로세스없음)로 설정한다.
프로세스가 요청된 PID에 시그널을 보낼 권한이 없는 경우 kill()은 실패하고, errno는 EPERM으로 설정한다.
2. Raise() 시그널 전송
프로세스 자기 자신에게 시그널을 보낼 때 사용된다.
예제는 생략한다.
#include <sys/types.h>
#include <signal.h>
int raise(int sig) ;
성공하면, '0'을 리턴, 에러가 발생하면 '0'이 아닌 값을 리턴
3. killpg()를 통한 시그널 전송
프로세스 그룹의 모든 맴버에게 시그널을 전송한다.
#include <sys/types.h>
#include <signal.h>
int killpg(pid_t pgrp, int sig) ;
성공하면, '0'을 리턴, 에러가 발생하면 '-1'값을 리턴
|