2014년 7월 2일 수요일

[리눅스 커널] 시스템 콜

reference : 리눅스 커널 심층 분석, 로버트 러브 지음, 황정동 옮김

시스템 콜

커널과 통신

시스템 콜은 하드웨어와 사용자 공간 프로세스 사이에 있는 계층이다. 이 계층은 다음과 같은 세 가지 역할을 한다.

첫째, 사용자 공간에 하드웨어 인터페이스를 추상화된 형태로 제공(파일 입출력 시 애플리케이션은 디스크나 저장 매체의 형식이나 파일시스템 형식 같은 것을 신경 쓸 필요가 없다.
둘째, 시스템 콜은 시스템 보안 및 안정성을 제공(커널이 시스템 자원과 사용자 공간 사이에서 중재자 역할을 하기 때문에, 커널이 접근권한과 같은 기준을 적용해 통제할 수 있다. 예를 들면, 하드웨어를 잘못 사용하거나 다른 프로세스의 자원을 빼았는 등의 동작을 막을 수 있다.
셋째, 사용자 공간(user-space)과 기타 시스템 사이에 계층을 둠으로써 프로세스별 가상 시스템 환경을 제공할 수 있다. 만약 애플리케이션이 아무런 제약없이 시스템 자원에 접근할 수 있다면 멀티태스킹이나 갓아 메모리를 구현하는 것은 거의 불가능에 가깝다.

리눅스의 시스템 콜은 사용자 공간에서 커널과 상호작용할 수 있는 유일한 수단이다. 트랩(trap)을 제외하면 시스템 콜은 정상적인 방법으로 커널로 진입하는 유일한 수단이다. 장치파일이나 /proc 파일시스템을 이용하는 다른 인터페이스도 결국은 시스템 호출을 통하게 되어 있다.

APIs, POSIX, and the C Library

애플리케이션은 일반적으로 시스템 콜을 직접 사용하지 않고, 사용자 공간에 구현된 애플리케이션 프로그래밍 인터페이스(API, Application Programming Interface)를 이용한다. 이 때문에 애플리케이션이 사용하는 인터페이스와 커널이 제공하는 인터페이스 사이에 직접적인 연관이 없다는 점이 아주 중요하다. API는 애플리케이션이 사용하는 프로그래밍 인터페이스다. 이 인터페이스는 하나 또는 그 이상의 시스템 콜을 사용해 구현되며, 경우에 따라 시스템 콜을 전혀 사용하지 않을 수도 있다. 또 시스템에 따라 내부 구현이 전혀 달라도(시스템 콜 구현 방식 등이 달라도) 같은 형태의 API를 사용함으로써 시스템이 달라도 애플리케이션에 동일한 인터페이스를 제공할 수도 있다. 다음 그림은 POSIX API와 C Library, 시스템 콜 사이의 관계를 보여준다.


유닉스에서 가장 유명한 API는 바로 POSIX 표준이다. 기술적인 용어로 말하자면, POSIX는 유닉스 기반 운영체제 간의 이식성 제공을 목적으로 정해진 IEEE 표준의 집합이다. 리눅스도 역시 POSIX 및 SUSv3 표준을 따른다.
POSIX는 API와 시스템 콜간의 관계를 보여주는 아주 훌륭한 예다. 대부분의 유닉스 시스템은 POSIX에 정의된 API와 시스템 콜 간에 밀접한 대응 관계를 가지고 있다. 사실 POSIX 표준은 초기 유닉스 시스템의 인터페이스를 본따 만든 것이다.
리눅스의 시스템 콜 인터페이스는 다른 유닉스 시스템과 마찬가지로 C Library 형태로 제공된다. C Library에는 표준 C Library와 시스템 콜 인터페이스 등 유닉스 시스템의 주요 API가 구현되어 있다. 이러한 C Library는 모든 C 프로그램이 사용하며, C의 특성으로 인해 다른 프로그래밍 언어의 프로그램에서 사용할 수 있게 감싼 형태를 쉽게 만들 수 있다. C Library는 대다수의 POSIX API를 추가로 제공한다.
애플리케이션 개발자 관점에서 보면 시스템 콜은 중요하지 않고 API에만 관심이 있다. 커널은 시스템 콜만 신경 쓴다. 어떤 라이브러리가 콜하는지 어떤 애플리케이션이 시스템 콜을 사용하는지에 대해서는 커널이 신경쓰지 않는다. 하지만 커널은 어떤 시스템 콜이 많이 사용될지, 시스템 콜의 유연함을 어떻게 유지할 것인지를 염두에 두어야 한다.

시스콜(Syscalls)
리눅스에서는 대체로 시스템 콜을 시스콜이라고 줄여서 부른다. 시스템 콜은 보통 C Library에 정의된 함수를 호출하는 방식으로 사용한다. 이 함수는 0개 혹은 하나 이상의 인자(arguments)를 받고, 하나 이상의 side effect를 발생시킬 수 있다. 파일에 데이터를 기록하거나 지정한 포인터에 특정 데이터를 저장하는 동작 등을 예로 들 수 있다. 시스템 콜은 성공과 실패에 대한 정보를 제공하는 long 형의 값을 반환하기도 한다.(64비트 아키텍처의 호환성을 위해 long 형을 사용한다.) 일반적으로 오류가 발생한 경우에는 음수값을 반환하고, 성공한 경우에는 0을 반환한다(항상 그런 것은 아님). 시스템 콜에서 오류가 발생할 경우 C Library는 전역 변수인 errno에 특정 오류코드를 기록한다. 라이브러리 함수인 perror()를 사용하면 이 변수의 값을 사람이 보기 편한 문자열 형태로 바꿀 수 있다.
시스템 콜은 정의된 특정 동작을 수행한다. 예를 들어, getpid()라는 시스템 콜은 현재 프로세스의 PID 값에 해당하는 정수값을 반환한다.이 시스콜은 커널에서 아주 간단하게 구현된다.

SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current);        // current->tgid 값을 반환한다.
}

함수 정의로는 구현 내용에 대해 아무것도 알 수 없다는 점에 주의하자. 커널은 시스템 콜이 의도하는 동작을 반드시 제공해야 하지만, 결과가 정확하기만 하다면 구현 방식은 어떻게 해도 상관 없다.
SYSCALL_DEFINE0는 인자가 없는(그래서 0이 붙음) 시스템 콜을 정의하는 매크로다. 이 매크로는 실제로 다음과 같이 확장된다.

asmlinkage long sys_getpid(void)

시스템 콜을 정의하는 방법을 살펴보자. 먼저, 함수 정의 부분에 asmlinkage 지시자가 있다. 이 지시자는 해당 함수의 인자를 스택에서만 찾으라고 컴파일러에게 알려준다. 모든 시스템 콜에는 이 지시자가 사용된다. 그다음 함수의 반환값은 long 형이다. 32비트 시스템과 64비트 시스템 간의 호환성을 유지하기 위해 사용자 공간에서 int형을 반환하는 시스템 콜은 커널 내부에서는 long 형을 반환한다.마지막으로 getpid() 시스템 콜은 커널 내부에서는 sys_getpid()라는 이름으로 정의된다. 이는 리눅스의 모든 시스템 호출이 사용하는 명명규칙이다. bar()라는 시스템 콜이 있으면 커널에는 sys_bar()라는 함수로 구현한다.

2014년 6월 24일 화요일

[리눅스 커널] 커널 자료구조

reference : 리눅스 커널 심층 분석, 로버트 러브 지음, 황정동 옮김
6장 커널 자료구조

1. 연결 리스트
연결 리스트는 노드라고 부르는 가변적인 개수의 데이터를 저장하고 관리하는 기능을 제공한다. 정적 배열과 달리 연결 리스트는 동적으로 데이터를 새엇ㅇ해 리스트에 추가할 수 있다. 그러므로 컴파일 시점에 미리 개수를 알 수 없는 데이터를 관리할 수 있다. 데이터가 한꺼번에 동시에 만들어지지 않으므로, 이 데이터는 인접한 메모리 공간에 모여 있지 않을 수 있다.따라서 데이터를 서로 연결시키는 방법이 있어야 하므로 리스트의 각 데이터에는 다음 데이터의 위치를 가리키는 next 포인터가 들어 있다. 리스트에 데이터를 추가하거나 삭제할 때는 다음 노드를 가리키는 포인터를 조정하면 된다.

단일 연결리스트와 이중 연결 리스트

연결 리스트를 나타내는 가장 단순한 자료구조의 형태는 다음과 같다.

/* 연결 리스트의 데이터 항목 */
struct list_element {
    void *data;                     /* 항목에 담긴 데이터(payload) */
    struct list_element *next; /* 다음 항목을 가리키는 포인터 */
};

이중 연결 리스트를 나타내는 자료구조의 형태는 다음과 같다.

/* 연결 리스트의 데이터 항목 */
struct list_element {
    void *data;                     /* 항목에 담긴 데이터(payload) */
    struct list_element *next; /* 다음 항목을 가리키는 포인터 */
    struct list_element *prev; /* 이전 항목을 가리키는 포인터 */
};

리눅스 커널의 연결 리스트는 독특한 방식으로 구현되지만, 본질적으로는 환형 이중 연결 리스트라고 할 수 있다. 이런 연결 리스트 형식을 사용함으로써 유연성을 최대한 확보할 수 있다.

연결 리스트 내에서 이동

연결 리스트 내의 이동은 선형으로 일어난다. 한 항목을 참조하고, 다음 포인터를 따라가 다음 항목을 참조한다. 이 과정을 반복하면 된다. 전체 리스트를 차례대로 훑는 작업이나 항목을 동적으로 삽입하고 제거하는 작업이 필요한 경웨 연결 리스트를 사용한다.
연결 리스트 구현에서 리스트의 '시작'부분에 접근하기 쉽게 리스트의 첫 번째 항목을 head라고 부르는 특별한 포인터로 표시하는 경우가 많다.


리눅스 커널의 구현 방식

연결 리스트 구조체

2.1 커널 개발 과정에서 커널의 연결 리스트 공식 구현이 도입되었다. 이제 모든 연결 리스트는 이 공식 구현 방법을 사용한다.
연결 리스트 코드는 <linux/list.h> 헤더 파일에 정의되며, 자료구조는 아래와 같이 간단하다.

struct list_head {
    struct list_head *next;
    struct list_head *prev;
};

이러한 구현 방식의 실제 유용성은 list_head 구조체의 사용법에 있다.

struct fox {
    unsigned long tail_length;    /* 꼬리의 길이(cm) */
    unsigned long weight;         /* 무게(kg) */
    bool is_fantastic;               /* 멋있는 여우인가? */
    struct list_head list;             /* fox 구조체 리스트 */
};

이렇게 하면 fox 구조체의 list.next는 다음 항목을 가리키고, list.prev는 이전 항목을 가리키게 할 수 있다. 커널은 이런 연결 리스트를 조작하는 일군의 함수를 제공한다. 예를 들어, list_add()를 사용하면 기존 연결 리스트에 새로운노드를 추가할 수 있다. 게다가 이런 함수는 범용성을 갖추고 있다. list_head 구조체만을 대상으로 처리하기 때문이다. container_of() 매크로를 사용하면 특정 변수 항목을 가지고 있는 부모 구조체를 쉽게 찾아낼 수 있다. C에서는 구조체내 특정변수의 상대적인위치(offset)가 컴파일 시점에 ABI 수준에서 고정되기 때문이다.

#define container_of(ptr, type, member) ({
    const typeof( ((type *)0)->member ) *__mptr = (ptr);
    (type *) ( (char *) __mptr - offsetof(type, member) );})

container_of() 매크로를 사용하면 list_head가 들어 있는 부모 구조체를 반환하는 함수를 간단하게 정의할 수 있다.

#define list_entry(ptr, type, member) container_of(ptr, type, member)

커널은 list_entry()를 비롯한 연결 리스트 생성, 조작 등의 관리 함수를 제공하며, 이런 함수는 list_head가 들어 있는 구조체에 상관없이 동작한다.


리스트 헤드

일반적으로 리스트 노드가 아닌 연결 리스트 자체를 가리키는 데 사용하는 특별한 포인터가 필요한 경우가 있다. 재미있게도 기본 list_head 구조체가 이 특별한 노드 역할을 할 수 있다.

static LIST_HEAD(fox_list);

이렇게 하면 fox_list라는 이름의 list_head 구조체를 초기화한다. 연결리스트와 관련된 다수의 함수는 하나 또는 두 개의 인자를 받는다. 헤드 노드 하나를 받거나 헤드 노드와 실제 리스트 노드를 받는다.


연결 리스트 조작

커널은 연결 리스트를 조작하는 일군의 함수를 제공한다. 모든 함수는 C 인라인 함수로 구현되어 있으며, <linux/list.h> 파일에 들어 있다.
이런 함수는 모두 O(1)이다. 즉 리스트나 다른 입력의 크기에 상관없이 일정한 시간 안에 실행된다.

연결 리스트에 노드 추가

연결 리스트에 노드를 추가하려면 다음 함수를 사용한다.

list_add(struct list_head *new, struct list_head *head)

이 함수는 head 노드 바로 뒤에 new 노드를 추가한다. 일반적으로 환형 리스트에는 첫 번째나 마지막 노드라는 개념이 없으므로 head에 어떤 항목을 지정해도 상관없다. '마지막' 항목을 전달한다면, 이 함수로 stack을 구현할 수 있다.
// 뒤에 추가하면서 뒤에서 빼면 스택 구현 가능
fox 리스트의 예로 돌아와서 fox_list 리스트에 새로운 struct fox를 추가해야 한다면 다음과 같이 한다.

list_add(&f->list, &fox_list);
// struct fox f; 인듯
// 연산자 우선순위 "->" > "&" 이므로 f->list 에 접근(포인터를 통해 요소 선택)하여 list의 주소(&)를 전달

연결 리스트의 마지막에 노드를 추가하려면 다음 함수를 사용한다.

list_add_tail(struct list_head *new, struct list_head *head)

이 함수는 head 노드 바로 앞에 new 노드를 추가한다. list_add()와 마찬가지로 환형 리스트이므로 head에 어떤 항목을 지정해도 상관없다. '첫 번째' 항목을 전달한다면, 이 함수로 큐를 구현할 수 있다.
// 뒤에 추가하면서 앞에서 빼면 큐 구현 가능

연결 리스트에서 노드 제거

연결 리스트에 노드를 추가하는 것 다음으로 리스트에서 노드를 제거하는 것이 가장 중요한 동작일 것이다. 연결 리스트에서 노드를 제거하려면 list_del() 함수를 사용한다.

list_del(struct list_head *entry)

이 함수는 리스트에서 entry 항목을 제거한다. 이 함수는 entry나 entry가 들어 있는 구조체가 차지하고 있던 메모리를 해제하지는 않는다. 보통 이 함수를 호출한 다음 list_head와 list_head가 들어 있는 자료구조의 메모리를 해제해야 한다.
예시

list_del(&f->list);

함수의 입력에 fox_list가 없다는 점에 주목하자. 이 함수는 특정 노드만을 입력으로 받아 해당 노드의 이전 및 다음 노드의 포인터를 조정해 해당 노드를 리스트에서 제거한다. 구현 내용을 보면 이해가 쉬울 것이다.

static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}

static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

연결 리스트에서 노드를 제거하고 다시 초기화할 때를 위해 커널은 list_del_init() 함수를 제공한다.

list_del_init(struct list_head *entry)

이 함수는 주어진 list_head를 초기화한다는 점만 제외하면 list_del() 함수와 같다. 해당 항목을 리스트에서 제거해야 하지만 자료구조 자체는 재사용이 필요한 경우에 이 함수를 사용한다. ( ??? )


2014년 6월 22일 일요일

[리눅스 시스템 프로그래밍] 6 세션과 프로세스 그룹

세션 시스템 호출

셸은 로그인 시점에 새로운 세션을 생성한다. 이러한 작업은 새로운 세션을 쉽게 생성하는 특수 시스템 호출을 사용해서 수행한다.
#include <unistd.h>

pid_t setsid (void);
setsid()는 새로운 세션 내부에 새로운 프로세스 그룹을 생성하며, 호출한 프로세스를 세션과 프로세스 그룹 모두의 리더로 만든다.(새로운 세션에서 제어하는 tty가 없는 유일한 구성원이 된다.) 이는 데몬이나 셸에 유용한 특성이다. 데몬은 세션 구성원과 터미널 제어를 원하지 않으며, 셸은 사용자가 로그인할 때마다 새로운 세션 생성을 원하기 때문이다.

여기서 tty는
유닉스 전문 용어로 tty는 단순히 읽기와 쓰기를 넘어 몇 가지 추가적인 명령어를 지원하는 디바이스 파일을 뜻합니다. 그리고 터미널은 사실상 이 tty와 동일한 의미로 사용됩니다. 일부 tty는 하드웨어 디바이스를 위해 커널이 제공하며 그 예로 키보드에서 들어오는 입력과 텍스트 모드 화면으로 나가는 출력, 시리얼 라인을 통해 전송되는 입출력이 있습니다. 그 외의 tty는 씬 커널 레이어를 통해 터미널 에뮬레이터라고 불리는 프로그램으로 제공되며 이런 tty를pseudo-tty로 부르기도 합니다. 터미널 에뮬레이터로는 Xterm(X 윈도우 시스템), Screen(프로그램과 다른 터미널 사이에 독립적인 계층을 제공), SSH(한 머신에서 프로그램으로 다른 머신의 터미널에 연결), Expect(스크립팅 터미널 인터랙션용) 등이 있습니다.
http://www.myservlab.com/144

성공하면 setsid()는 새롭게 생성된 세션의 세션 ID를 반환한다.
실패하면 호출은 -1을 반환하며 유일하게 가능한 errno 코드는 EPERM이다.(프로세스가 현재 프로세스 그룹 리더라는 사실을 알려준다.) 특정 프로세스가 프로세스 그룹 리더가 되지 않도록 만드는 확실한 방법은 fork이며,  부모 프로세스가 종료된 다음에 자식 프로세스가 setsid()를 수행하면 된다. 예를 들면 다음과 같다.


pid_t pid;

pid = fork ();
if (pid == -1) {
    perror ("fork");
        return -1;
    } else if (pid != 0)
        exit (EXIT_SUCCESS);

    if (setsid () == -1) {
        perror ("setsid");
        return -1;
    }

조금 덜 유용하긴 하지만, 현재 세션 ID를 얻으려면 다음과 같은 함수를 사용한다.

#define _XOPEN_SOURCE 500
#include <unistd.h>

pid_t getsid (pid_t pid);

getsid()는 pid가 가리키는 프로세스의 세션 ID를 반환한다. 오류 발생시 -1을 반환한다. 유일한 errno 값은 ESRCH로, pid가 유효한 프로세스에 대응하지 않음을 알려준다.
이 기능을 실제로 활용하는 경우는 드물며, 주로 진단 목적으로 쓰인다.

pid_t sid;

sid = getsid (0);
if (sid == -1)
    perror ("getsid"); /* 가능하지 않은 경우다. */
else
    printf ("My session id=%d\n", sid);


프로세스 그룹 시스템 호출

setpgid()를 호출하면 pid가 가리키는 프로세스의 그룹 ID를 pgid로 설정한다.

#define _XOPEN_SOURCE 500
#include <unistd.h>

int setpgid (pid_t pid, pid_t pgid);

-p230

출처 : 리눅스 시스템 프로그래밍, 로버트 러브 지음, 박재호 옮김, 한빛미디어 2009년 펴냄

2014년 6월 2일 월요일

[Security] SSH

Secure Shell

Secure Shell (SSH) is a cryptographic network protocol for secure data communication, remote command-line login, remote command execution, and other secure network services between two networked computers. It connects, via a secure channel over an insecure network, a server and a client running SSH server and SSH client programs, respectively.[1] The protocol specification distinguishes between two major versions that are referred to as SSH-1 and SSH-2.
The best-known application of the protocol is for access to shell accounts on Unix-like operating systems, but it can also be used in a similar fashion for accounts on Windows. It was designed as a replacement for Telnet and other insecure remote shell protocols such as the Berkeley rsh and rexec protocols, which send information, notably passwords, in plaintext, rendering them susceptible to interception and disclosure using packet analysis.[2] The encryption used by SSH is intended to provide confidentiality and integrity of data over an unsecured network, such as the Internet.

- http://en.wikipedia.org/wiki/Secure_Shell

2014년 5월 5일 월요일

[Python] 공부 20140505

파이썬은 바이트코드를 생성한다.
garbage collection 기능
regular expression 지원
대소문자를 구분한다.

divmod(9, 5)
(1, 4)

경로 확인방법
$which python


  • vim

O : 행 위에 삽입

.py파일의 앞에
#!/usr/bin/python
or
#!/usr/bin/env python -> 이식성을 위해

파이썬 인터프리터 안(혹은 다른 모듈)에서 모듈 파일을 수행하기 위한 방법은 두 가지가 있다.
1) execfile 이용
>>>execfile('modfile.py')

2) import로 수행 -> 이쪽이 바람직한 듯
>>>import modfile
>>>modfile.s      # 모듈 안에 정의된 s를 이용할 수 있다.

연속라인
줄바꾸기 앞의 '\'는 다음 라인을 현재 라인과 연결시켜 주는 역할을 한다.

치환문(assignment), 대입문
>>>a = 1
변수의 형은 우측의 객체에 의해서 결정된다.
한 번에 치환
>>>c, d = 3, 4
>>>x=y=z=0
>>>e=0.3; f=3.4
>>>e, f = f, e        # 값의 교환


  • 이름과 객체
파이썬은 다른 많은 언어와는 달리 변수의 이름과 값 객체가 분리되어 있다. 예를 들어, a = 1이란 문에서 숫자 1이 변수 a에 저장되는 것이 아니다. 파이썬에서는 a를 이름, 숫자 1을 객체라고 하며, 두 개념은 분리되어 있다.
파이썬과 같은 동적인 자료형을 가지는 언어에서는 변수의 생성과 소멸이 언제든지 일어날 수 있기 때문에 이름이 주소로 변환되지 않고 별도의 장소에 이름이 보관되며, 이름과 객체와읭 관계 정보를 추가로 가지고 있다.(소스코드를 기계어 수준으로 번역해서 실행하는 컴파일러언어에서는 이름이 실제 메모리 주소로 변환된다.) 이름이 저장되는 장소를 심볼 테이블(Symbol Table)이라고 한다.


  • eval() 내장 함수

문자열로 된 파이썬 expression(식)을 실행한다.
>>>a=1
>>>a=eval('a+4')
>>>a
5


  • exec

문자열로 된 statement(문)을 수행한다.
>>>a=5
>>>exec 'a = a + 4'
>>>a
9


  • compile (나중에 볼 것)

exec나 eval은 문자열로 입력된 코드를 분석해서 파이썬 컴파일 코드로 변환한다.



  • 콘솔 입력

키보드로부터 문자열을 읽어 들이기 위해서 사용하는 함수는 raw_input이다. 엔터키를 입력할 때까지 읽은 문자열을 리턴한다.
>>>name = raw_input('name?')
name?ildelusion
>>>print name
ildelusion

# 정수나 실수 등의 값을 입력 받기 원한다면
>>>k = int(raw_input('int : '))

>>>i = input('int : ')
# input은 입력된 문자열을 파이썬 식으로 처리해서 넘겨준다.


문자열은 값이 변경되지 않기 때문에 변경을원하면 슬라이싱과 연결하기를 이요앟ㄴ다.
>>>s = 'Hello World!'
>>>s = 'h' + s[1:]
>>>s
'hello World'

멤버십 테스트
>>>'World' in s
True


  • 리스트

>>>L = [1, 2, 3]
>>>len(L)    # 데이터 개수
>>>L[1]      # 두 번째 자료
>>>L[1:3]   # 슬라이싱
>>>L+L
[1, 2, 3, 1, 2, 3]
>>>L*3        # 반복
>>>L.append(4)     # 리스트 마지막에 자료 추가
>>>del L[0]     # 리스트 자료 삭제
>>>L.reverse()      # 리스트 순서를 바꾼다.
>>>L.sort()     # 리스트를 오름차순으로 정렬한다.


  • 튜플

- 튜플의 값은 변경이 안 되고, 리스트는 변경이 가능하다. 튜플은 리스트처럼 다양한 메소드를 가지지 않는다.
>>>t = (1, 2, 3)
>>>len(t)
3
>>>t[0]
1
>>>t(-1)
3
>>>t[0:2]
(1, 2)
>>>t + t + t     # 연결
>>>t * 3      # 반복
>>>3 in t
True

[Ubuntu, putty] vim color

putty에서 우분투에 접속할 때 일부 색상이 검은 바탕에 파란 글씨로 나오길래 설정을 바꿨다.

vim /home/[user_name]/.vimrc

에서

color desert

를 추가하였다.


다음은 현재 사용중인 shell을 확인하는 명령어

echo $SHELL

2014년 4월 10일 목요일

[Ubuntu] 하드디스크 새로 추가, 제거, 포맷


gparted를 받아서 설치하면 됩니다.
gparted는
GParted is a GTK+ front-end to GNU Parted and the official GNOME Partition Editor application besides Disks.
It is used for creating, deleting, resizing, moving, checking and copying partitions, and the file systems on them. This is useful for creating space for new operating systems (works with Windows Vista / 7 System & Data partitions), reorganizing disk usage, copying data residing on hard disks and mirroring one partition with another (disk imaging).
이라고 합니다. 즉 GUI로 구현한 디스크 파티션 프로그램입니다.
$ sudo apt-get install gparted


you must specify the filesystem type
had trouble writing out superblocks.
등의 문제를 모두 해결해줍니다.

저는 sdb1을 추가해주려고 했습니다.
1. 오른쪽 상단에서 sdb를 선택
2. 장치(Device) -> 새 파티션 테이블 만들기
3. 아래에서 /dev/sdb 우클릭, 다음으로 포맷 -> ext4
4. 위 메뉴에서 초록색? v(체크표시) 클릭
-> 진행 됩니다.

그리고
$ sudo mkdir /data
# 여기서 data는 마운트할 디렉토리
$ sudo vi /etc/fstab
에서 마지막줄에
/dev/sdb1    /data    ext4    defaults    0    1
를 추가해주고 :wq (vi 종료)
# 파티션 자동 마운트를 위해 fstab에 저 한 줄을 추가해준 것입니다.
$ sudo mount -a
# fstab에 설정된 모든 파일시스템을 마운트합니다.
$ df -hT
# 파일시스템 및 디스크 용량 확인


reference
[1] http://liveegg.blogspot.kr/2013/03/blog-post.html
[2] http://nearbyacademy.tistory.com/55


2014년 1월 30일 목요일

[리눅스 시스템 프로그래밍] wait() 시스템 콜

자식 프로세스가 종료될 때 완전히 사라진다면, 부모 프로세스가 참고할 수 있는 정보들이 남지 않는다. 따라서 유닉스의 초기 설계자들은 자식 프로세스가 부모에 앞서 죽으면, 커널이 자식 프로세스를 특수한 프로세스 상태로 들어가게 만들어야 한다고 결정했다. 이런 상태에 있는 프로세스를 좀비라고 한다. 유용할지도 모르는 자료를 포함하는 몇 가지 기본적인 커널자료 구조처럼 프로세스를 지탱하는 최소 뼈대만 보존한다. 부모가 자신의 상태를 조사하도록 이런 특수한 프로세스 상태에 있는 자식 프로세스는 부모 프로세스를 기다린다(이런 자식 프로세스를 좀비 프로세스라고 부른다). 부모가 종료된 자식에 대해 보존된 정보를 획득한 다음에야 공식적으로 자식 프로세스가 종료되고 좀비에서 벗어나게 된다.


위의 프로세스 상태 전이 표에서 우측 상단에서 EXIT_ZOMBIE를 볼 수 있다.

리눅스 커널은 종료된 자식에 대한 정보를 얻기 위한 몇 가지 인터페이스를 제공한다. POSIX에 정의된 가장 단순한 인터페이스로  wait()가 있다.

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait (int *status);

wait()를 호출하면 종료된 자식의 pid를 반환하며, 오류가 발생하면 -1을 반환한다. 자식 프로세스가 종료되지 않았다면 호출은 자식이 종료될 때까지 차단된다. 자식 프로세스가 이미 종료되었으면 호출은 즉시 반환된다. 따라서 SIGCHLD를 받고나서 자식 프로세스가 사망했다는 소식을 접한 다음에 wait()를 호출할 경우 차단(blocking) 없이 즉시 반환된다.

* SIGCHLD - 프로세스가 종료될 때 커널은 SIGCHLD 시그널을 부모에게 보낸다.

오류가 발생하면 다음 두 가지 errno 값 중 하나로 설정된다.

ECHILD 호출한 프로세스에 자식 프로세스가 존재하지 않는다.

EINTR 대기 중에 시그널을 받았으며, 조기에 호출이 반환된다.

NULL이 아닌 status 포인터는 자식에 대한 추가 정보를 포함한다. POSIX는 status에서 확인을 원하는 비트만 정의하도록 구현을 허용하므로, 표준은 이런 매개 변수를 해석하기 위해 매크로 군을 제공한다.

#include <sys/wait.h>

int WIFXITED (status); /* 프로세스가 정상적으로 종료되면 참을 반환. 즉 프로세스가 _exit를 호출한 경우 */
int WIFSIGNALED (status); /* 시그널이 프로세스 종료를 초래했을 경우 참을 반환 */
int WIFSTOPPED (status); /* 프로세스가 중단되거나 */
int WIFCONTINUED (status); /* 진행을 재개할 경우 참을 반환 */

int WEXITSTATUS (status); /* WIFXITED가 참을 반환하면 _exit()로 넘긴 값을 하위 8비트에 담아서 제공 */
int WTERMSIG (status); /* WIFSIGNALED가 참을 반환하면 종료를 초래한 시그널 번호를 반환 */
int WSTOPSIG (status); /* WIFSTOPPED가 참을 반환하면 프로세스를 멈춘 시그널 번호를 제공 */
int WCOREDUMP (status); /* 프로세스가 시그널을 받아서 코어 파일을 만들어낼 경우 참을 반환 */

0이 아닌 값은 참이다.
코어덤프에 대한 내용

자식에게 어떤 일이 일어났는지 파악하기 위해 wait()를 사용하는 예제 프로그램을 살펴보자.

                                                                                                                                  
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main (void)
{
int status;
pid_t pid;

if (!fork ())         // 호출 성공하면 자식 프로세스는 0을 반환
return 1;            // 자식 프로세스 종료

pid = wait (&status); // status를 설정
if (pid == -1)
perror ("wait");

printf ("pid=%d\n", pid);

if (WIFEXITED (status))
printf ("Normal termination with exit status=%d\n", 
WEXITSTATUS (status));

if (WIFSIGNALED (status))
printf ("Killed by signal=%d%s\n",
WTERMSIG (status),
WCOREDUMP (status) ? " (dumped core)" : "");

if (WIFSTOPPED (status))
printf ("Stopped by signal=%d\n",
WSTOPSIG (status));

if (WIFCONTINUED (status))
printf ("Continued\n");

return 0;
}
                                                                                                                                  

이 프로그램은 자식 프로세스를 fork해서 바로 종료한다. 그리고 나서 부모 프로세스는 wait() 시스템 호출을 수행해서 자식 프로세스 상태를 파악한다. 프로세스는 자식 프로세스 pid와 그것이 어떻게 죽었는지를 출력한다. 이 경우 자식 프로세스는 main()에서 반환되는 방법으로 종료되었기 때문에 다음과 유사한 결과를 얻는다.

                                                                                                                                  
$ ./wait
pid=8520
Normal termination with exit status=1
                                                                                                                                  

프로그래머가 return을 사용하는 대신에 SIGABRT 시그널을 자신에게 보내는 abort()를 호출했을 경우 다음과 유사한 결과를 얻는다.

                                                                                                                                  
$ ./wait
pid=8678
Killed by signal=6
                                                                                                                                  

abort()는 <stdlib.h>에 정의되어 있다.
예제 코드를 다음과 같이 바꾼다.
if (!fork ())
        abort ();


출처 : 리눅스 시스템 프로그래밍, 로버트 러브 지음, 박재호 옮김, 한빛미디어 2009년 펴냄, ISBN: 978-89-7914-679-0 93569

[리눅스 시스템 프로그래밍] 프로세스 종료 (Terminating a Process)

#include <stdlib.h>

void exit (int status);

exit() 호출은 몇 가지 종료 단계를 수행한 다음에 커널에게 프로세스를 종료하라고 지시한다.
status 매개 변수는 프로세스 종료 상태를 지정하기 위해 사용한다.

프로세스를 종료하기에 앞서, C 라이브러리는 다음 종료 단계를 순서대로 수행한다.
1. atexit()나 on_exit()로 등록된 함수를 등록 순서의 역순으로 호출한다.
2. 모든 표준 입출력 스트림을 강제로 비운다.
3. tmpfile() 함수로 만든 임시 파일을 삭제한다.

이런 단계를 거쳐 사용자 영역에서 프로세스가 수행해야 하는 모든 작업을 마쳤다면, exit()는 시스템 콜인 _exit()를 불러 커널이 프로세스 종료 과정에 필요한 나머지 작업을 수행하게 한다.

#include <unistd.h>

void_exit (int status);

어플리케이션에서 _exit()를 직접 호출할 수 있지만, 이런 방식은 거의 의미가 없다. 대다수 어플리케이션은 stdout 스트림을 강제로 쓰는 작업(flushing)과 같이(위의 작업 2.) exit가 제공하는 정리 기능 몇 가지를 요구하기 때문이다.

프로그램을 끝내는 전통적인(classic) 방법은 명시적인 시스템 콜이 아니라 단순히 프로그램 '끝까지 진행하는' 방법이다.  C 프로그램에서는 main() 함수가 반환될 때 이런 일이 일어난다. 하지만 '끝까지 진행하는' 방법에서도 여전히 시스템 콜을 부른다. 컴파일러는 독자적인 종료 코드 다음에 암묵적으로 _exit()를 넣어버린다. exit()나 main() 반환값을 사용해서 종료 상태를 명시적으로 반환하도록 프로그램을 작성하는 관례는 바람직하다. 셸은 종료값을 확인해서 명령이 성공했는지 실패했는지를 파악한다. eixt(0)이거나 main() 반환값이 0이면 성공을 나타낸다.

또한 기본 동작이 프로세스 종료를 의미하는 시그널을 받을 경우 프로세스는 종료한다. (SIGTERM, SIGKILL 같은 시그널)


#include <stdlib.h>

int atexit (void (*function)(void));

atexit() 라이브러리 호출은 프로세스 종료 과정에서 수행할 함수를 등록하기 위해 쓰인다.
atexit() 호출이 성공하면 인수로 넘어온 함수를 정상적인 프로세스 종료 과정 중에 수행하도록 등록한다. 다시 말해, exit() 수행이나 main()에서 반환되어 프로세스가 종료될 때, 등록된 함수가 수행된다. 프로세스가 exec 계열 함수를 수행하면 등록된 함수 목록이 깨끗하게 초기화된다. 프로세스가 시그널을 받고 종료하면 등록된 함수는 호출되지 않는다.
함수는 매개 변수를 받지 않으며, 반환값도 없다. 서식(prototype)은 다음과 같다.

void my_function (void);

함수는 등록 역순으로 수행된다. 다시 말해, 함수는 LIFO 방식으로 스택에 저장된다. 등록된 함수는 exit()를 호출해서는 안 된다. exit()를 호출할 경우 무한 재귀 호출(an endless recursion)이 일어난다.

간단한 예제

                                                                                                                                  

atexit()와 같은 동작을 하는 on_exit() 함수도 있지만 최신 솔라리스 버전에서는 더 이상 이 함수를 지원하지 않으므로 표준을 따르는 atexit() 함수를 대신 사용해야 한다.

2014년 1월 29일 수요일

그라데이션

잠이 안와서 결국 일곱시까지 깨있네요... 그래도 오랜만에 이런 멋진 장면도 보네요.


[리눅스 시스템 프로그래밍] fork() ,exec 시스템 콜의 의미(Meaning of fork() and exec system call)

리눅스 커널 공부를 1년 전 쯤에 했다.  fork(), exec 함수군에 대해서 읽어 봤지만 이건 당최 무슨 소리인지 알 수가 없었다. 요즘「리눅스 시스템 프로그래밍(로버트러브 저)」을 읽으니까 이 함수들이 어떤 역할을 하는지 대충 알 것 같다.
시스템 프로그래밍(여기서 의미하는 것은 시스템 콜)을 먼저 공부하고 커널 공부를 했더라면... 하는 생각이 든다. 커널을 공부하기 위해서는 시스템 콜을 어느정도 알고있어야 된다고 생각한다.

1. fork() 시스템 콜은 현재 프로세스와 동일한 이미지로 동작하는 새로운 프로세스를 만든다.
(A new process running the same image as the current one can be created via the fork()
system call)
2. exec 함수군(execl(), execlp(), execle(), execv(), execvp(), execve())을 호출하면 path가 가리키는 프로그램을 메모리에 올리는 방법으로 현재 프로세스 이미지를 새로운 프로세스 이미지로 대체한다.
(A call to execl() replaces the current process image with a new one by loading into
memory the program pointed at by path)

"쓰기 후 복사(COW:copy-on-write)는 fork의 경우 더욱 큰 장점을 제공한다. fork 다음에 exec가 뒤따를 확률이 아주 높으므로 부모 주소 공간을 자식 주소 공간으로 복사하는 행위는 종종 완전한 시간 낭비다. 자식이 즉시 새로운 바이너리 이미지를 실행한다면 이전 주소 공간은 완전히 제거된다. COW는 이런 경우를 위해 최적화를 수행한다."

(Copy-on-write has yet a bigger benefit in the case of forking. Because a large percentage of forks are followed by an exec, copying the parent’s address space into the child’s address space is often a complete waste of time: if the child summarily executes a new binary image, its previous address space is wiped out. Copy-on-write optimizes for this case.)

다음은 fork 다음에 exec가 뒤따르는 코드이다.
(code that fork is followed by an exec)

_________________________________________________________________________________
1  pid_t pid;
2  
3  pid = fork();
4  if (pid == -1)
5   perror ("fork");
6  
7  /* 자식 프로세스 */ /* child process */
8  if (!pid) {
9   const char *args[] = { "windlass", NULL };
10 int ret;
11
12    ret = execv ("/bin/windlass", args);
13 if (ret == -1) {
14 perror ("execv");
15 exit (EXIT_FAILURE);
16 }
17 }
_________________________________________________________________________________

자식 프로세스에서는 fork() 호출이 성공하면 0을 반환한다. 부모 프로세스에서 fork()는 자식 프로세스의 pid를 돌려준다. line 8에서 pid가 0인 것을 검사하고 자식 프로세스의 코드를 실행하는데 자식 프로세스는 0을 반환하기 때문이다.
새로운 자식을 얻었다는 사실을 제외하고는 부모 프로세스는 변함없이 계속해서 돌아간다.  execv()호출은 자식이 /bin/windlass 프로그램을 실행하도록 변경한다.