| 검색 | ?

1.1. GNU/Linux 구조

image00.png
[PNG image (16.31 KB)]


image02.png
[PNG image (1.87 KB)]
  • Linux는 Kernel, Libc, Application으로 구성되는데 이를 통틀어서 GNU/Linux 라고 부릅니다.
  • GNU/Linux는 크게 Kernel영역과 User영역으로 접근권한에 의해 나뉘어집니다.
  • User영역으로부터 System Call Interface에 의해서 Kernel영역의 함수를 호출합니다.
  • Kernel영역에서는 모든 물리적 장치의 접근제어가 가능하지만 User 영역에서는 직접적인 접근에는 많은 제약이 있으며 User영역에서의 물리적 장치접근을 위해서는 Kernel영역으로 SCI를 통해서 요청하는 식으로 동작하게 됩니다.
  • Libc부분은 GNU C Library(glibc) 가 가장 많이 사용되고 Embedded 환경에서는 크기가 비교적 작은 uClibc 등이 많이 사용됩니다.

1.2. Linux kernel의 구성요소

image03.png
[PNG image (1.74 KB)]
  • Linux kernel은 그림과 같이 Architecture의존적인 부분, Device driver들, Memory관리, Process 및 Thread관리, 가상파일시스템, NetworkStack, SCI로 구성되어 있습니다. 커널은 이러한 자원을 사용자들의 요구에 대하여 적절히 중재하여 관리하는 역할을 담당합니다.

1.3. Linux kernel image의 구조

image04.png
[PNG image (992 Bytes)]
  • Bootsector은 Floppy boot 등을 위한 boot sector code로 이루어집니다. 보통 Bootloader에 의해서 부팅하는 경우 이 부분은 사용하지 않거나 건너뜁니다.
  • Setup은 기초적인 Memory 설정이나 압축을 풀기위한 사전단계를 진행하는 code로 이루어집니다. Bootloader에 의해서 부팅하는 경우 Setup 으로 jump 하게 됩니다.
  • Head+Misc는 압축된 Kernel 영역을 해제하는 code들로 이루어집니다.

1.4. VFS

image06.png
[PNG image (1.53 KB)]
  • Linux의 특징중에 수많은 파일시스템에 대하여 여러가지 Interface추상화를 제공 한다는 점이 있습니다.
  • 파일시스템들은 사용하지 않는 메모리를 활용한 Buffer cache자원에 도움을 받아서 물리적 저장장치의 접근을 최적화 합니다.
  • procfs, devfs, sysfs등 물리적 저장장치가 아닌 어떤 제어를 목적으로 만들어진 가상 파일시스템도 하나의 파일시스템으로 취급합니다.

1.4.1. Loopback 장치

실제 디바이스 장치와 동일하게 동작하도록 하지만 실제로는 하나의 가상장치에 속하는 것을 Loopback 장치라고 합니다.

하나의 파일에 특정 파일시스템의 형식을 갖추어 저장한후 이것을 하나의 장치로 보도록 하여 mount 할수 있는데 이러한 경우 Loopback 파일시스템이라고 합니다.

1.4.2. Ram-disk

특별한 저장장치가 아닌 메모리 그 자체를 저장의 용도로 하나의 디스크처럼 사용할수 있도록 해놓은것을 Ram-Disk장치라고 합니다. 이것은 Loopback 장치처럼 특정 파일시스템으로 포맷할수도 있고 mount할수도 있으며 일반 저장장치보다 메모리를 이용하기 때문에 매우 빠른 read/write/search가 가능하다는것이 장점이지만 전원이 꺼지면 그 자료도 날아간다는 점이 단점이라고 할수 있습니다.

부팅시에는 Loopback 장치로 인식하는 Initial ram-disk를 rootfs으로 하여 복잡한 부팅요구조건을 해결하기도 합니다.

1.5. Process간의 통신지원

가) Signal : 정의된 이벤트를 비동기적으로 알리기 위해서 사용합니다. 나) Pipe : VFS inode를 이용한 통신방식입니다. 이는 redirection등에 많이 사용됩니다. 다) Socket : Network통신을 위한 자원입니다. 대부분 BSD socket interface를 통해서 구현됩니다. 라) System V IPC : MessageQueue, Semaphore, Shared memory등이 IPC자원을 통해서 구현됩니다.

1.6. Memory 할당체계

Kernel에서의 memory할당은 Virtual alloc과 Physical alloc이 수반되며 할당함수는 크게 연속적인 물리공간을 확보하기 위한 함수와 그렇지 않아도 되는 함수로 분류될수 있습니다.

  • Contiguous allocation
    • get_free_pages : Buddy알고리즘에 의한 메모리 할당 함수로 가장 최하위에 있는 함수라고 할수 있습니다.
    • kmalloc, kmem_cache_alloc : Slab할당 알고리즘에 의하여 구현된 할당자입니다.
  • Uncontiguous allocation
    • vm_alloc : 물리적으로 연속적이지 않은 메모리 공간을 할당합니다.

1.7. Linux의 부팅절차

  1. Board power ON
  2. BIOS에 의해서 정해진 Post 절차를 수행하고 Bootsector를 읽어서 제어권을 넘김
  3. Bootsector로부터 Bootloader로 제어권을 넘김
  4. Bootloader 로부터 Kernel image를 읽어들임. (bsetup 부분으로 제어권 넘김)
  5. bsetup 부분에서 필요한 메모리설정이나 기반사항 초기화후 head+misc로 제어권 넘김
  6. head+misc에서 실제 압축된 커널 이미지를 메모리로 압축해제하고 압축해제된 커널로 제어권 넘김.
  7. 제어권을 넘겨받은 커널은 Bootloader로부터 넘겨받은 인자가 있는 경우 이를 확인 (시스템마다 다를수 있음)
  8. 커널인자 root= 에 해당하는 인자가 주어지거나 기본값이 있는 경우 해당 root fs을 mount 하고 init= 에 해당하는 인자가 주어지거나 기본값이 있는 경우 이를 실행
  9. init= 인자에 의해서 주어진 init process는 /etc/inittab을 확인하고 sysinit 항목에 주어진 실행파일을 실행합니다.
    • 일부 Embedded system에서는 init process를 진행하지 않고 linuxrc process를 실행하도록 하는 경우도 있습니다.
    • init process이후의 절차는 배포판마다 다소 차이가 있으며 전혀 다른 수행절차를 넣을수도 있는 부분입니다.
    • init process는 항상 ProcessID가 1인 프로그램으로 모든 Application의 최상위 Process로 존재하며 하위 process들을 관리하는 역할을 수행합니다.
  10. sysinit의 실행이 끝나면 respawn 항목으로 지정한 터미널장치를 생성하며 보통은 getty 가 이를 처리하게 됩니다.
  11. getty는 login 프로그램을 실행하여 사용자로부터 login 을 요구받는 프롬프트를 생성합니다.
  12. login은 login에 합당한 조건이 만족하게 되면 /bin/sh를 실행하여 shell사용권한을 사용자에게 줍니다.

1.8. Shared Library버젼에 따른 호환

Linux에서는 Shared Library가 보통 파일명이 lib<name>-<version>.so<> 과 같은식으로 주어집니다. 그리고 Libary를 만들때 주어지는 soname에 의해서 이 library의 호환유무가 결정되어집니다.

어떤 실행프로그램 A가 어떤 shared library를 link하였을때 A는 실행할때는 해당 shared library의 soname으로 주어진 파일명을 찾게 됩니다. 그래서 과거에 만들었던 shared library와 구조가 많이 틀어져서 새로운 shared library가 빌드되어도 서로 다른 soname을 부여하여 빌드하여 이 library를 사용하는 프로그램들이 공존할수 있게 합니다.

만약 내가 빌드한 프로그램이 어떤 library를 사용하고 어떤 soname을 이용하는지 알고 싶으면 "ldd"라는 명령어로 이를 확인할수 있습니다.

1.9. Linux 의 directory구조

  • / : 최상위 directory로 반드시 존재해야 하는 기준위치
  • /dev : System에서 사용하는 장치들에 대응하는 특수장치파일들이 있는곳이며 부팅시 반드시 필요.
  • /etc : 설정파일들이 저장되어 있는 directory
  • /bin : 기본적인 프로그램들의 실행파일이 있는 directory
  • /sbin : 운영상의 목적에 필요한 실행파일들이 있는 directory
  • /lib : 기본적으로 사용되는 library들이 저장되어 있는 directory, (ld-linux, libc, libdl, libtermcap, libpthread 등이 위치)
  • /proc : system의 정보를 제공하거나 제어를 제공하는 가상파일이 있는 directory
  • /var : system이 동작하면서 빈번히 수정이 되거나 생성되는 파일들이 저장되는 directory
  • /usr : 사용자들의 프로그램과 라이브러리 및 기타 부수적인 파일들이 저장되어 있는 directory
  • /mnt : mount 를 하기 위한 directory (여기에만 mount할수 있는것은 아님)
  • /home : 일반 사용자 계정의 home directory들이 만들어지는 곳
  • /boot : Bootloader 에 의해서 부팅하게될 Kernel image들이 저장되는 directory
  • /root : root 계정의 home directory

1.10. Real Time Linux (RT-Linux)를 위한 Scheduler관련 조작

Linux는 기본적으로 Hard RTOS는 아니며 Soft RTOS를 지원하는 기능을 가지고 있습니다.

아래의 함수들은 CAP_SYS_NICE특권을 가진 User Application에서 호출할수 있는 RealTime OS의 동작을 위한 Scheduler제어함수들입니다.
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
int sched_getscheduler(pid_t pid);
  • 주어진 pid의 process에 대한 Schedule정책을 설정할수 있습니다.
  • policy는 SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR이 사용될수 있습니다.
    • SCHED_OTHER: 일반 process들이 이에 속하며 TimeSlice(quantum)를 할당받고 남은 TimeSlice를 기본우선순위로 취하는 방식으로 동작합니다. (동적우선순위 = 남은 TimeSlice + (20 - nice))
    • SCHED_FIFO: SCHED_OTHER보다 높은 우선순위를 가지며 TimeSlice의 개념이 없는 방식으로 동작합니다. 즉, 스스로 CPU를 반환하지 않는동안은 CPU를 선점합니다.
    • SCHED_RR: SCHED_OTHER보다 높은 우선순위를 가지며 TimeSlice가 존재하고 같은 우선순위의 process는 Round-Robin 방식으로 Schedule동작을 합니다.
    • SCHED_BATCH: Idle schedule policy 개념으로 실행되는 process가 없을때 동작하는 방식으로 Real-Time과 반대라고 보시면 됩니다.
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
  • 주어진 정책의 우선순위의 범위를 확인하기 위해서 사용합니다.
int sched_setparam(pid_t pid, const struct sched_param *param);
int sched_getparam(pid_t pid, struct sched_param *param);
  • 주어진 pid의 process에 대한 Schedule정보설정 및 확인을 위해 사용합니다.
int sched_yield(void);
  • 이 함수를 호출한 process의 schedule을 다음순위로 양도합니다.
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
  • 특정 CPU를 사용하거나 사용하지 않도록 합니다.
  • CPU_ZERO, CPU_SET, CPU_CLR macro와 함께 사용합니다.
int nice(int inc);
  • Schedule정책이 SCHED_OTHER인 프로세스에서는 nice값을 조정하여 TimeSlice와 우선순위에 영향을 줍니다.
  • Schedule정책이 SCHED_RR인 프로세스에서는 TimeSlice에만 영향을 줍니다.

예제소스
/*
    Copyright (C) HWPORT.COM
    All rights reserved.
    Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
*/

#if !defined(_ISOC99_SOURCE)
# define _ISOC99_SOURCE (1L)
#endif

#if !defined(_GNU_SOURCE)
# define _GNU_SOURCE (1L)
#endif

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <sched.h>

int main(int s_argc, char **s_argv);

int main(int s_argc, char **s_argv)
{
    pid_t s_pid;
    cpu_set_t s_cpu_set;
    int s_check;
    int s_cpu_count;
    int s_current_cpu_running_index;
    int s_cpu_index;

    s_pid = getpid();

    if(s_argc >= 2) {
        s_check = atoi(s_argv[1]);
        if(s_check > 0) {
            s_pid = (pid_t)s_check;
        }
    }
    else {
        (void)fprintf(
            stdout,
            "usage: %s <pid> [<cpuid> [...]]\n\n",
            basename(s_argv[0])
        );
    }

    if(s_argc >= 3) { /* 특정 CPU만 사용하도록 하는 경우 */
        int s_arg_index;

        CPU_ZERO(&s_cpu_set);

        for(s_arg_index = 3;s_arg_index < s_argc;s_arg_index++) {
            s_cpu_index = atoi(s_argv[s_arg_index]);
            if(__builtin_expect(s_cpu_index <= 0, 0)) {
                (void)fprintf(
                    stderr,
                    "invalid argument ! (argv[%d]=\"%s\")\n",
                    s_arg_index,
                    s_argv[s_arg_index]
                );
            }
            else {
                CPU_SET((size_t)s_cpu_index, &s_cpu_set);
            }
        }

        s_cpu_count = (int)CPU_COUNT(&s_cpu_set);
        if(__builtin_expect(s_cpu_count <= 0, 0)) {
            (void)fprintf(stderr, "not assigned cpu ! (invalid argument ?)\n");
        }
        else {
            s_check = sched_setaffinity(s_pid, sizeof(s_cpu_set), (cpu_set_t *)(&s_cpu_set));
            if(__builtin_expect(s_check == (-1), 0)) {
                perror("sched_setaffinity");
                return(EXIT_FAILURE);
            }
        }
    }

    s_check = sched_getaffinity(s_pid, sizeof(s_cpu_set), (cpu_set_t *)(&s_cpu_set));
    if(__builtin_expect(s_check == (-1), 0)) {
        perror("sched_getaffinity");
        return(EXIT_FAILURE);
    }

    /* 주어진 pid에 해당하는 프로그램이 사용할 수 있는 사용가능한 CPU 개수 */
    s_cpu_count = (int)CPU_COUNT(&s_cpu_set);

    /* 현재 sched_getcpu 호출시점에서 사용중인 CPU 번호 */
    s_current_cpu_running_index = (int)sched_getcpu();

    (void)fprintf(stdout, "CPU_USING_COUNT=%d (pid=%ld)\n", s_cpu_count, (long)s_pid);

    for(s_cpu_index = 0;(s_cpu_index < s_cpu_count) && (s_cpu_index < ((int)CPU_SETSIZE));s_cpu_index++) {
        (void)fprintf(
            stdout,
            "\tCPU%d : %s%s\n",
            s_cpu_index + 1,
            (CPU_ISSET((size_t)s_cpu_index, &s_cpu_set) == 0) ? "not used" : "used",
            (s_cpu_index == s_current_cpu_running_index) ? " (current running)" : ""
        );
    }

    return(EXIT_SUCCESS);
}

/* vim: set expandtab: */
/* End of source */

1.11. Process 관리

전통적인 Unix system에서는 Process를 생성할때 부모Process로부터 자원을 복제하여 새로운 Process를 생성합니다. 그러나 이렇게 자원을 모두 복제하는 경우 자식Process가 모든 자원을 동일하게 모두 활용하는것이 아니고 그 일부분만을 사용하게 되므로 비효율적인 자원소모가 있다가 판단하여 Linux에서는 다음과 같은 해결방안을 사용합니다.
  • Copy on write 방식 적용 : 자식Process 생성시 모든 자원은 부모Process로부터 복제되지 않은 상태에서 시작하며 자식Process가 실제 물리적 page에 접근을 할때 자원이 복제되는 방법
  • Light weight process : 부모Process와 자식Process의 주소공간을 공유하고 실행문맥에 필요한 정보만 별도로 운영하는 방식
  • vfork : 부호Process와 자식Process의 메모리 주소 공간을 공유
uid_t getuid(void);
uid_t geteuid(void);
int setuid(uid_t uid);
int seteuid(uid_t euid);
gid_t getgid(void);
gid_t getegid(void);
int setgid(gid_t gid);
int setegid(gid_t egid);

pid_t fork(void);
pid_t vfork(void);

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

void exit(int status);

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

int system(const char *command);

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t thread);


pid_t pid = vfork();
if(pid == ((pid_t)(-1))) perror("fork");
else if(pid == ((pid_t)0)) { /* 자식 process */
    char * const arg[] = { "/bin/ls", "-al", (char *)0 };
    (void)execvp(arg[0], arg);
    exit(0);
}
else (void)waitpid(pid, (int *)0, 0); /* 부모 process */

1.12. Signal

  • Signal은 process들에게 특별한 Event에 대해서 반응하거나 조치할수 있도록 하기 위해서 사용됩니다.
  • Signal은 상대 process가 어떤 수행상태에 있는지 상관없이 발생시킬수 있습니다.
  • 수행중이 아닌 process에게 signal을 보내면 해당 process가 깨어나면서 Signal을 처리하게 됩니다.
  • Signal을 전송했으나 아직 처리되지 않은 Signal은 Pending signal이라고 하며 Pending signal을 반복해서 보내는 경우는 무시됩니다.
  • SIGKILL과 SIGSTOP signal은 예외적으로 block시킬수 없습니다.

    SIGHUP    SIGINT     SIGQUIT   SIGILL    SIGTRAP   SIGABRT    SIGIOT    SIGBUS
    SIGFPE    SIGKILL    SIGUSR1   SIGSEGV    SIGUSR2   SIGPIPE    SIGALRM   SIGTERM
    SIGSTKFLT SIGCHLD    SIGCONT   SIGSTOP    SIGTSTP   SIGTTIN    SIGTTOU   SIGURG
    SIGXCPU   SIGXFSZ    SIGVTALRM SIGPROF    SIGWINCH  SIGIO      SIGPOLL   SIGPWR
    SIGSYS
    


    typedef void (*sighandler_t)(int);
    
    sighandler_t signal(int signum, sighandler_t handler);
    int kill(pid_t pid, int sig);
    int raise(int sig);
    int pause(void);
    
    unsigned int alarm(unsigned int seconds); /* SIGALRM */
    

1.13. File descriptor의 접근

리눅스는 다양한 장치들을 특별한 파일처럼 취급합니다.
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int fcntl(int fd, int cmd, ... /* arg */ );
int ioctl(int d, int request, ...);
int remove(const char *pathname);
DIR *opendir(const char *name);
int readdir(unsigned int fd, struct old_linux_dirent *dirp, unsigned int count);
int closedir(DIR *dirp);
int dup(int oldfd);
int dup2(int oldfd, int newfd);
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
int symlink(const char *oldpath, const char *newpath);
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
int utime(const char *filename, const struct utimbuf *times);
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);

1.14. Inter Process Communication (IPC)

  • IPC는 Data의 전송과 공유, Event전송, 자원의 공유, Process제어등을 목적으로 만들어진 특별한 Process간 통신수단입니다.
  • 다음과 같은 IPC응용자원이 리눅스에서 제공됩니다.
    • pipe (반이중 통신)
    • FIFO (named pipe)
    • stream pipe (전이중 통신)
    • named stream pipes
    • Message queue
    • Semaphore
    • Socket
    • Stream

  • /* pipe */
    int pipe(int pipefd[2]);
    
    /* stream pipe */
    FILE *popen(const char *command, const char *type);
    int pclose(FILE *stream);
    
    /* fifo */
    int mkfifo(const char *pathname, mode_t mode);
    int mknod(const char *pathname, mode_t mode, dev_t dev);
    
    /* 세마포어 */
    int semget(key_t key, int nsems, int semflg);
    int semctl(int semid, int semnum, int cmd, ...);
    int semop(int semid, struct sembuf *sops, unsigned nsops);
    
    /* 메시지큐 */
    int msgget(key_t key, int msgflg);
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    
    /* 공유메모리 */
    int shmget(key_t key, size_t size, int shmflg);
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    int shmdt(const void *shmaddr);
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    

1.15. Linux의 시간

  • 기본적으로 Timer interrrupt에 의존하는 tick수를 전역변수 jiffies으로 Interrupt tick마다 "++jiffies"을 수행하여 Tick을 count 합니다.
  • 처음 부팅을 하면 jiffies값은 0부터 시작하며 이것을 가지고 초기 RTC로부터 읽어와 그 시간을 하시고 System time을 관리하게 되며 jiffies를 단순히 msec단위로 환산하면 부팅후의 시간인 uptime을 유추할수 있게 됩니다.
  • Linux에서 Timer interrupt의 주기는 기본적으로 대부분의 시스템은 100Hz 로 동작하도록 설계됩니다. 하지만 100Hz주기로 동작하게 되면 jiffies값은 10msec마다 한번씩 Tick count를 하게 되므로 1msec단위의 세부 시간관리를 할수 없게 됩니다. 이를 확장하기 위해서 Kernel (menu)config 에 보면 HZ 상수를 변경할수 있도록 되어 있으며 이것을 1000Hz로 변경하면 msec단위의 시간관리가 가능해집니다.
  • 일반 PC에서는 i686 이하 시스템의 경우 8254 timer를 이용하기 때문에 10msec이하로 Timer tick 을 사용할수 없습니다. 이는 곧 일반 PC에서는 10msec이하는 사용하지 않습니다. 그러나 i686기존 이상부터는 HPET timer를 이용하여 보다 정밀하게 사용가능합니다.
  • 요즘에서는 CPU에 공급되는 clock에 대한 tick count를 register로 읽어볼수 있는 경우가 많습니다. 하지만 기본적으로 Linux에서는 그러한 자원을 직접적으로 제공하지 않습니다. 만약 CPU clock 자원을 이용하려면 Assembly level에서 직접 해당 register를 읽어서 사용해야 합니다. (이것은 Op자체도 Clock을 소모하기 때문에 커널에서 User mode로 해당 자원을 지원한다는게 의미가 크지 않다고 판단하는것 같습니다.)

1.15.1. uptime

  • uptime은 시스템이 부팅한 이후부터 얼마나 경과되었는지를 말하는 시간기준입니다.
  • 간단히 이를 확인하려면 Shell에서 "cat /proc/uptime"명령으로 확인가능합니다.
  • User mode에서 C언어로 uptime을 얻어오려면 "sysinfo"라는 함수를 이용할수 있습니다.
    #include <sys/sysinfo.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int s_argc, char **s_argv)
    {
        struct sysinfo s_sysinfo_local;
    
        (void)s_argc;
        (void)s_argv;
    
        if(sysinfo((struct sysinfo *)(&s_sysinfo_local)) == 0) {
            (void)fprintf(stdout, "uptime is %ld sec\n",
    (long)s_sysinfo_local.uptime);
        }
    
        return(EXIT_SUCCESS);
    }
    
  • uptime은 시스템이 부팅된후부터의 시간을 말하므로 어떤 방법으로든 시스템이 켜져있는 동안에는 시간의 역으로 흐르지 않으며 임의로 변경할수 없는 자원입니다.

1.15.2. System time은?

  • 일반적으로 Shell 에서 "date"명령으로 확인가능한 시간을 말합니다. 즉, 일반적인 우리가 알고 있는 시계자원입니다.
  • 리눅스에서는 이것을 UTC로 설정하고 localtime zone정보에 의해서 변환하여 사용하는 방법을 많이 사용하며 Windows처럼 아예 UTC는 고려하지 않고 (Time offset을 사용하지 않는) 직접 Localtime zone으로 관리하는 방법로 사용가능합니다. 이러한 정보는 "/etc/localtime" 파일과 "/etc/adjtime"파일이 관여하게 됩니다.
  • System time은 C언어의 "time_t" type으로 저장되며 과거에는 32bit 크기를 가졌으나 요즘에는 64bit크기를 선호합니다.
  • User mode에서 C언어로 System time을 얻어오려면 "time"이라는 함수를 이용할수 있습니다. 그리고 이것을 사람이 알아먹을수 있는 Local time으로 얻어오려면 "localtime"함수를 이용할수 있으며 UTC time으로 얻어오려면 "gmtime"이라는 함수를 이용할수 있습니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main(int s_argc, char **s_argv)
    {
        time_t s_time_local;
    
        if(time((time_t *)(&s_time_local)) != ((time_t)(-1))) {
            struct tm s_tm_local;
    
            (void)memcpy((void *)(&s_tm_local), localtime((time_t
    *)(&s_time_local)), sizeof(struct tm));
            (void)fprintf(stdout, "지역시간: %d/%d/%d %02d:%02d:%02d\n",
    s_tm_local.tm_year + 19000, s_tm_local.tm_mon + 1, s_tm_local.tm_mday,
    s_tm_local.tm_hour, s_tm_local.tm_min, s_tm_local.tm_sec);
    
            (void)memcpy((void *)(&s_tm_local), gmtime((time_t
    *)(&s_time_local)), sizeof(struct tm));
            (void)fprintf(stdout, "그리니치 표준시: %d/%d/%d %02d:%02d:%
    02d\n", s_tm_local.tm_year + 19000, s_tm_local.tm_mon + 1,
    s_tm_local.tm_mday, s_tm_local.tm_hour, s_tm_local.tm_min,
    s_tm_local.tm_sec);
    
        }
    
        return(EXIT_SUCCESS);
    }
    
  • 반대로 시스템 시간을 내가 원하는 시간으로 설정하려면 다음과 같이 할수 있습니다.
    #include <time.h>
    
    ...
    
    struct tm s_set_tm_local;
    time_t s_set_time_local;
    
    (void)memset((void *)(&s_set_tm_local), 0, sizeof(s_set_tm_local));
    s_set_tm_local.tm_year = <년도> - 1900;
    s_set_tm_local.tm_mon = <월> - 1;
    s_set_tm_local.tm_mday = <일>;
    s_set_tm_local.tm_hour = <시>;
    s_set_tm_local.tm_min = <분>;
    s_set_tm_local.tm_sec = <초>;
    s_set_tm_local.tm_isdst = <일광절약시간 적용유무>;
    #if defined(_BSD_SOURCE)
    s_set_tm_local.tm_gmtoff = <UTC로부터 지역시간대의 시간>; /* -timezone */
    s_set_tm_local.tm_zone = <지역시간대 이름>; /* tzname[ s_set_tm_local.tm_isdst ] */
    #endif
    s_set_time_local = mktime((struct tm *)(&s_set_tm_local)); /* struct tm구조체형식을 time_t 형식으로 변환해주며 tm구조체를 일부 보정하는 기능이 포함되어 있습니다. */
    
    if(stime((time_t *)(&s_set_time_local)) == 0) {
        /* 설정완료 */
    }
    
    ...
    
  • 지역시간대로부터 UTC사이의 시간간격을 timezone이라고 하며 일광절약시간에 의해서 조정되는 시간차를 구하기 위해서는 다음과 같이 구할 수 있습니다.
    int s_tz_index;
    struct tm s_tm_local;
    time_t s_time[2]; /* [0]=standard time, [1]=daylight time */
    long s_delta_zone;
    long s_timezone;
    long s_altzone;
    #if defined(_WIN32) /* windows의 경우 */
    char s_tzname_local[2][ TZNAME_MAX ];
    size_t s_tzname_size;
    #endif
    const char *s_tzname[2];
    
    #if defined(_WIN32) /* windows의 경우 */
    _tzset();
    #else
    tzset();
    #endif
    
    for(s_tz_index = 0;s_tz_index < 2;s_tz_index++) {
    #if defined(_WIN32) /* windows의 경우 */
        (void)_get_tzname(
            (size_t *)(&s_tzname_size),
            (char *)memeset((void *)(&s_tzname_local[s_tz_index]), 0, sizeof(s_tzname_local[s_tz_index])),
            sizeof(s_tzname_local[s_tz_index]),
            s_tz_index
        );
        s_tzname[s_tz_index] = (const char *)(&s_tzname_local[s_tz_index]);
    #else
        s_tzname[s_tz_index] = tzname[s_tz_index];
    #endif    
    
        (void)memset((void *)(&s_tm_local), 0, sizeof(s_tm_local));
    
        s_tm_local.tm_year = 2012 - 1900;
        s_tm_local.tm_mon = 8 - 1;
        s_tm_local.tm_mday = 4;
        s_tm_local.tm_hour = 1;
        s_tm_local.tm_min = 30;
        s_tm_local.tm_sec = 0;
        s_tm_local.tm_isdst = s_tz_index;
    
        s_time[s_tz_index] = mktime((struct tm *)(&s_tm_local));
    }
    
    /* 일광절약시간에 의한 시간차 (일광절약시간이 사용된다면 통상적으로 3600초가 나옵니다.) */
    s_delta_zone = (long)(((long)s_time[0]) - ((long)s_time[1]));
    
    /* 지역시간대로부터 UTC와의 시간차 (대한민국의 경우 -32400 초 [ -9시간 ]) */
    #if defined(_WIN32) /* windows의 경우 */
    _get_timezone(&s_timezone);
    #else
    s_timezone = timemzone;
    #endif
    
    /* 일광절약시간을 함께 포함한 지역시간대로부터 UTC와의 시간차  */
    s_altzone = s_timezone - s_delta_zone;
    
    /* 지역시간대 및 일광절약 표기 */
    (void)printf("%s%ld%s%ld\n", s_tzname[0], s_timezone, s_tzname[1], s_altzone);
    
    /* UTC로부터의 시간차 표기 */
    (void)printf("PST%ldPDT%ld    (UTC%ld)\n", -s_timezone, -s_altzone, -s_timezone);
    
  • System time은 전원이 꺼지면 초기화가 됩니다. 즉, RTC자원과는 다른 자원입니다. 만약 전원이 꺼졌다가 켜져도 시간이 계속 유지하도록 하려면 RTC자원으로부터 부팅시에 System time을 설정하는 단계가 필요합니다. (일반 리눅스에서는 이것을 "/sbin/hwclock"명령어가 수행하게 됩니다.)
  • System time을 "time", "stime"함수로 이용하면 초단위의 시간으로만 관리할수 있는 반면 gettimeofday, settimeofday를 이용하면 microsecond단위로 관리할수 있습니다. 하지만 실제로 Microsecond단위까지 정밀성을 보장하지 못하며 이것은 커널의 HZ상수가 정의하는 기준에 의존하여 정밀성을 갖습니다. 즉, HZ상수가 100인경우 10msec단위의 정밀도를 갖게 됩니다.

1.15.3. System clock tick 자원

  • 이것은 uptime과 흡사하고 거의 동일한 자원으로 구현되었다고 보지만 시스템마다 좀더 높은 정밀도를 위하여 구현되기도 하기 때문에 Windows의 Multimedia timer 자원과 흡사하다고 보시면 됩니다. 결국 이자원 역시 jiffies로부터 시간자원을 이용하는 방식을 이요하는 시스템들이 많기 때문에 jiffies의 tick 주기(HZ상수)에 의존합니다. 하지만 일부 Embedded CPU vendor들에서는 이것이 전용 고급 Timer 자원으로부터 구현되는 경우가 있어서 보통은 이 자원을 많이들 사용합니다. 그리고 시간의 역류현상이 uptime과 마찬가지로 존재하지 않기 때문에도 많이 사용합니다.
  • User mode에서 C언어로 System clock tick을 얻어오려면 "times"라는 함수를 이용할수 있으며 Clock tick 주기는 "sysconf(_SC_CLK_TCK)" 함수로 얻어올수 있습니다. Clock tick 주기는 대부분의 시스템들은 jiffies의존방식에 의해서 HZ상수가 되지만 Embedded CPU vendor들중 일부는 이것을 좀더 높은 정밀성으로 구현합니다. 아래의 예제소스는 이 자원을 이용하여 msec 단위의 상대시간값을 구할수 있는 Time Stamp 함수를 구현한것입니다.
  • 아래의 예제에서 times 함수의 반환값을 이러한 용도로 활용할수 있는 경우도 있습니다. 하지만 이 사항은 커널의 구현방식을 확인하는게 필요하며 경우에 따라서 특정 시간이 지나면 Overflow에 대한 고려가 있어야 합니다. (의외로 필자의 경험상 times의 구현은 매우 혼란스러운 Overflow 대응고려가 필요합니다.)
    • 일부환경에서는 times 함수의 반환값 clock() / HZ 가 부팅직후 289초 쯤에서 Overflow가 -1135, -1134, 1133, 1132, 1131, 1130, .., 1, 0, 1, 2, 3, 4, ... 와 같은식으로 발생합니다. 그리 좋은 Overflow 방식은 아닌듯 싶습니다.
  • #include <sys/time.h>
    #include <time.h>
    #include <unistd.h>
    
    typedef unsigned long long __mz_time_stamp_t;
    #define mz_time_stamp_t __mz_time_stamp_t
    
    typedef struct mz_timer_ts {
    
        /* need shadow */
        long clock_tick;
        clock_t prev_clock;
        mz_time_stamp_t time_stamp;
    
    }__mz_timer_t;
    #define mz_timer_t __mz_timer_t
    
    static mz_time_stamp_t __mz_timer_time_stamp(mz_timer_t *s_timer)
    {
        clock_t s_clock;
    
    #if defined(__linux__)
        /* get linux kernel's jiffes (tick counter) */
        s_clock = times((struct tms *)0);
    #else
        do {
            struct tms s_tms;
            s_clock = times((struct tms *)(&s_tms));
        }while(0);
    #endif
    
        if(s_clock == ((clock_t)(-1))) {
            /* overflow clock timing */
            return(s_timer->time_stamp);
        }
    
        if(s_timer->clock_tick <= 0l) {
            /* get ticks per second */
            s_timer->clock_tick = sysconf(_SC_CLK_TCK);
            if(s_timer->clock_tick <= 0l) {
                /* invalid clock tick */
                return(s_timer->time_stamp);
            }
    
            s_timer->prev_clock = s_clock;
        }
    
        /* update time stamp (clock to upscale) */
        s_timer->time_stamp += (((mz_time_stamp_t)(s_clock -
    s_timer->prev_clock)) * ((mz_time_stamp_t)1000)) /
    ((mz_time_stamp_t)s_timer->clock_tick);
        s_timer->prev_clock = s_clock;
    
        return(s_timer->time_stamp);
    }
    
    static mz_time_stamp_t mz_get_time_stamp_msec(mz_timer_t *s_timer)
    {
        statuc mz_timer_t g_global_timer_local = {
            0l, (clock_t)0, (mz_time_stamp_t)1000u
        };
    
        return(__mz_timer_time_stamp((mz_timer_t
    *)(&g_global_timer_local));
    }
    

1.15.4. POSIX규격의 clock_gettime 시간자원

  • 좀더 활용가치가 높은 시간자원으로는 POSIX규격에서 명시하고 있는 clock_gettime함수가 있습니다. (응용예: 절대적 순방향 시간자원 얻기)
  • "unistd.h" header에서 "_POSIX_VERSION" 과 "_POSIX_TIMER" 가 define되어 있는 환경이라면 clock_gettime 함수는 사용가능할겁니다.
  • POSIX계열 환경에서는 여러가지 시간자원을 clock_gettime 으로 모두 대체하는것을 권유하는 추세입니다.

1.15.5. 그 밖에 시간자원

  • Intel CPU의 경우 Real Time Stamp Clock 이라는 자원을 제공하는데 리눅스에서 직접적으로 제공하지 않지만 다음과 같이 Assmebly로 읽을수 있습니다. 사실상 가장 적확한 시간자원인 Real Time Stamp Clock은 대부분의 시스템에서 리눅스에 구현되지 않았지만 일부 Embedded system의 경우 System clock tick 자원으로 구현되는 경우가 많습니다.
    unsigned long long int mz_get_cpu_clock(void)
    {
        unsigned long long int s_qword;
    
        __asm__ volatile(
            "rdtsc\n\t" : "=A"(s_qword)
        );
    
        return(s_qword);
    }
    
  • 리눅스는 User mode에서는 Scheduler 를 직접적으로 제어할수 없기 때문에 Context반응에 의해서 다소 시간의 오차가 발생할수 있습니다. 대략 이것은 HZ상수와 Driver들의 구현방식에 의해서 영향을 받으며 보통 0~50msec정도의 오차율이 발생할수 있습니다. 이것은 Windows도 그렇기 때문에 잘못된 사항이 아닙니다. 하지만 Multi media를 다룰때 Jitter time 에 대한 범위가 RTOS보다는 조금 신경을 써야 한다는것뿐이 차이는 없습니다.
  • 리눅스는 Context 스위칭을 다음과 같은 경우에 일으킬수 있습니다. 시간자원은 이러한 Context 스위칭문맥에서 시간오차가 발생할수 있기 때문에 반드시는 아니지만 설계시에 고려하면 좋습니다.
    • sleep, usleep, nanosleep 등의 delay계열 함수
    • open, close, read, write, select, 등의 systemcall 함수들
    • sched_yield 함수 (본래부터 이 함수는 강제로 Context 스위칭을 일으키도록 의도하는 함수입니다.)
    • Timer interrupt를 포함한 모든 Interrupt 진입점
    • 만약 다음과 같이 무한루프내에 위의 Context 조건관련 함수호출이 없다면 Context 스위칭은 오로지 Interrupt 시점에서만 발생하여 시스템이 굉장이 느려질수 있습니다.
      for(;;);
      
    • 시스템이 느려지는것을 막기 위해서 위의 경우는 다음과 같이 조치해야 합니다.
      #define HZ 100
      for(;;)usleep((HZ/1000) * 1000); /* 이 경우 HZ상수에 따른 최적의
      sleep을 주는것이지만 그냥 usleep(10000) 으로 해도 무관합니다. */
      
  • 정밀성이 요구되는 Timer자원이 필요하다면 Posix timer도 검토할만합니다. 이것은 설정한 주기를 기준으로 정확히 Tick을 발생하도록 할수 있습니다.
    /*
      Copyright (C) JAEHYUK CHO
      All rights reserved.
      Code by JaeHyuk Cho <mailto:minzkn@minzkn.com>
    */
    
    #include <sys/types.h>
    #include <sys/time.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <signal.h>
    #include <time.h>
    #include <unistd.h>
    #include <errno.h>
    
    #include <pthread.h>
    
    #define def_mztimer_realtime_signal SIGRTMIN
        
    static long g_mztimer_interval_usec;
    
    static void mztimer_handler(int s_signal, siginfo_t *s_siginfo, void *s_user_context)
    {
        static unsigned int s_tick = 0;
        timer_t *s_timer_id_ptr;
        int s_overrun_count;
    
        (void)s_signal;
        (void)s_siginfo;
        (void)s_user_context;
    
        s_timer_id_ptr = (timer_t *)s_siginfo->si_value.sival_ptr;
        s_overrun_count = timer_getoverrun(*s_timer_id_ptr);
        if(s_overrun_count == (-1)) {
            s_overrun_count = 0;
        }
    
        (void)fprintf(stdout, "tick=%u, overrun_count=%d", s_tick, s_overrun_count);
    
    #if 1L /* DEBUG: compare with system time */
        do {
            static unsigned long long s_start_time_usec = 0ull;
            unsigned long long s_current_time_usec;
            struct timeval s_timeval;
            unsigned long long s_duration_usec, s_diff_usec;
    
            s_duration_usec = s_tick * g_mztimer_interval_usec;
    
            (void)gettimeofday(&s_timeval, NULL);
            s_current_time_usec = (s_timeval.tv_sec * 1000000) + s_timeval.tv_usec;
            if(s_start_time_usec == 0ull) {
                s_start_time_usec = s_current_time_usec;
            }
            s_current_time_usec -= s_start_time_usec;
    
    	if(s_duration_usec > s_current_time_usec) {
    	    s_diff_usec = s_duration_usec - s_current_time_usec;
    	}
    	else {
    	    s_diff_usec = s_current_time_usec - s_duration_usec;
    	}
    
            fprintf(stdout, ", real=%llu.%03llu, diff=%llu.%llu",
                s_current_time_usec / 1000ull,
                s_current_time_usec % 1000ull,
                s_diff_usec / 1000ull,
                s_diff_usec % 1000ull);
        }while(0);
    #endif
    
        (void)fprintf(stdout, "\n");
    
        s_tick += 1 + s_overrun_count;
    }
      
    static void *mztimer_thread(void *s_argument)
    {
        static timer_t s_timer_id;
        struct sigevent s_sigevent;
        struct itimerspec s_itimerspec;
        sigset_t s_mask;
        struct sigaction s_sigaction;
        clockid_t s_clock_id = CLOCK_REALTIME;
    
        /* establish handler for timer signal */
        s_sigaction.sa_flags = SA_SIGINFO;
        s_sigaction.sa_sigaction = mztimer_handler;
        sigemptyset(&s_sigaction.sa_mask);
        if(sigaction(def_mztimer_realtime_signal, &s_sigaction, NULL) == (-1)) {
            return(NULL);
        }
    
        /* block timer signal temporarily */
        sigemptyset(&s_mask);
        sigaddset(&s_mask, def_mztimer_realtime_signal);
        if(sigprocmask(SIG_SETMASK, &s_mask, NULL) == (-1)) {
            return(NULL);
        }
    
        /* create timer */
        s_sigevent.sigev_notify = SIGEV_SIGNAL;
        s_sigevent.sigev_signo = def_mztimer_realtime_signal;
        s_sigevent.sigev_value.sival_ptr = &s_timer_id;
        if(timer_create(s_clock_id, &s_sigevent, &s_timer_id) == (-1)) {
            return(NULL);
        }
    
        /* start the timer */
        s_itimerspec.it_value.tv_sec = g_mztimer_interval_usec / 1000000l;
        s_itimerspec.it_value.tv_nsec = (g_mztimer_interval_usec % 1000000l) * 1000l;
        s_itimerspec.it_interval.tv_sec = s_itimerspec.it_value.tv_sec;
        s_itimerspec.it_interval.tv_nsec = s_itimerspec.it_value.tv_nsec;
        if(timer_settime(s_timer_id, 0, &s_itimerspec, NULL) == (-1)) {
            return(NULL);
        }
       
        /* unlock the timer signal */
        if(sigprocmask(SIG_UNBLOCK, &s_mask, NULL) == (-1)) {
            return(NULL);
        }
    
        for(;;) {
            pause();
        }
    
        return(NULL);
    }
    
    int mztimer_start(long s_interval_usec)
    {
        pthread_t s_thread_handle;
        sigset_t s_mask;
    
        g_mztimer_interval_usec = s_interval_usec;
    
        if(pthread_create(&s_thread_handle, NULL, mztimer_thread, NULL) == (-1)) {
            return(-1);
        }
        (void)pthread_detach(s_thread_handle);
        
        sigemptyset(&s_mask);
        sigaddset(&s_mask, def_mztimer_realtime_signal);
        (void)sigprocmask(SIG_BLOCK, &s_mask, NULL);
    
        return(0);
    }
    
    int main(void)
    {
        (void)mztimer_start(1000l);
    
        for(;;) {
            
            sleep(1);
    	
    	(void)fprintf(stdout, "\x1b[1;33msleep loop\x1b[0m\n");
        }
        
    
        return(EXIT_SUCCESS);
    }
    
    /* End of source *
    


Copyright ⓒ MINZKN.COM
All Rights Reserved.