멀티 쓰레딩에서 데이터를 공유할 때 발생하는 문제

  • 멀티 쓰레딩의 장점은 쓰레드들간에 데이터를 쉽게 주고 받을 수 있다는 것
  • -> 그러나 여러 쓰레드가 동시에 접근할 때, 정확하게 데이터 값을 가질수 있도록 하는 것은 매우 어려운 문제
  • 하나의 쓰레드가 메모리 영역에 대해 확실하게 자신의 영역을 가지고 있는 경우 -> 각자 할당된 데이터 영역을 read, write하므로 문제가 생기지 않음
  • 같은 메모리 영역을 동시에 읽고 쓰는 경우?
    • ex)웹 서버 : 연결을 요청할 때마다 쓰레드를 만들어냄 -> 쓰레드들은 html파일을 메모리에 캐시형태로 보관 -> 여러개의 쓰레드들이 이 파일을 동시에 읽고 씀 -> 정확한 파일 값을 유지하기 어려워짐
    • 몇 명의 client가 접속해 있는지 저장하는 변수가 있다고 가정하고 예제를 확인해보자

  1. for루프를 돌면서 쓰레드 생성 - 이 경우는 2개의 worker thread
  2. worker thread는 50000번 만큼 cnt를 +해줌
  3. thread 1과 2가 있을 때, 두 쓰레드를 동시에 실행시킨다면 각각 50000번씩 총 100000이라는 cnt가 출력되어야 하는데 실제로는 100000보다 작은 수가 출력되는 것을 볼 수 있음
  • 이유

 

  1. 5만 번씩 반복하는 for문을 기계어로 풀었을 때의 모습
  2. cnt값을 메모리에서 레지스터로 읽어오는 load연산
  3. 레지스터의 cnt값을 update하는 부분
  4. 증가한 cnt값을 다시 메모리로 저장하는 store연산
  5. 위 세 과정이 한꺼번에 이루어짐(Critical section) -> 다른 무언가가 끼어들면 문제가 발생
  6. 동시에 수행되는 경우를 보자

  1. H -> L(cnt에서 레지스터로 값을 불러옴) -> U(+1하는 연산) -> S(더한 값을 다시 메모리에 저장)까지 thread 1 실행
  2. thread 2가 끼어들어서 H -> L(cnt값을 레지스터로 불러옴) -> U(+1) -> S(저장)
  3. 이 경우에는 우리가 원하는 결과를 얻을 수 있음
  4. 또다른 예제를 보자

  1. 아까와 마찬가지로 thread 1은 U까지 수행함 그러나 저장하기 전에 thread 2가 수행된다면
  2. L에서 cnt값을 읽어올때 thread 1에서 저장하지 않았으므로 +1을 하기 전인 0을 불러옴
  3. 불러온 후 thread 1의 값이 저장(S)이 됨
  4. thread 2가 다시 실행되어 저장까지 되면 그대로 1이 저장이 되는 결과가 나타남

멀티쓰레딩 프로그램의 정확성 보장 방법

  • 안전하지 않은 쓰레드들의 수행을 동기화 시켜줌(Synchronize)
    • 동시에 수행되어 문제가 발생할 수 있는 부분은 하나의 쓰레드만 수행할 수 있도록 정해줌(mutually exclusive access)
    • 정확성을 유지할 수 있도록 충분한 조건을 주어 코딩
    • ex)여러가지 기능들 제공
      1. pthreads : semaphores, Mutexes, and condition variables, Locks and rwrite locks
      2. Java : Monitors

Mutexes(mutual exclusion)

  • special type을 가지는 변수 - pthread_mutex_t, pthread_mutexattr_t
  • 어떤 코드의 부분을 한 쓰레드만 수행할 수 있도록 지정해줌
  • pthread_mutex_init(2)/destroy(3)

  • 첫 번째 pthread_mutex_init : 선언한 mutex 변수를 우리가 원하는 attribute를 가지도록 설정해줌 -> NULL로 설정시 default값이 됨
  • 두 번째 : pthread_mutex_t mutex를 선언할 때, 뒤에 매크로인 PTHREAD_MUTEX_INITIALIZER을 써줌
  • 다 사용한 후 해제할 경우 pthread_mutex_destroy를 써줘서 해제
  • pthread_mutex_lock(2)/unlock(3)

  • pthread_mutex_lock : lock을 해주면 critical section으로 지정됨 -> 지정된 후 한 쓰레드가 수행중이라면 다른 쓰레드가 오더라도 동시에 수행하지 못하고 대기상태(suspend)가 되어 기다림
  • pthread_mutex_unlock : unlock을 거쳐서 쓰레드가 나가게 되면 대기중이었던 쓰레드가 수행을 시작
  • lock과 unlock은 항상 쌍으로 쓰여야함 -> unrock을 하지 않으면 한 쓰레드가 수행 후 다른 쓰레드들은 수행하지 못하게 됨
  • pthread_mutex_trylock : 이미 critical section에서 수행중인 쓰레드가 있다면 기다리지 않고 return해버림
  • 예제

  1. for문으로 여러개의 work 쓰레드를 생성
  2. worker는 cnt를 증가시킴 -> 50000번씩 반복
  3. mutex를 사용하지 않은 경우 원하는 결과가 나오지 않았으나 lock을 걸고 unlock으로 cnt를 감싸주면 쓰레드 하나가 실행중일때 다른 쓰레드는 대기
  4. unlock을 통해 한 쓰레드가 나가야지 비로소 대기중인 쓰레드가 수행하므로 원하는 결과값을 얻을 수 있게됨

 

 

 

 

 

 

여러가지 일들을 동시에 처리 -> multi threading

 

Thread의 개념

  • 프로세스란 프로그램이 실행되고 있는 것 -> 한 부분만 실행
  • 쓰레드를 사용하면 프로그램의 여러부분을 동시에 실행 가능 -> 각각의 부분의 쓰레드라고 함
  • function을 호출해가는 순서, local variable을 저장할 공간이 필요
    • 어느 부분을 하고 있는지를 가리키는 counter와 호출해가는 순서를 기록하는 stack을 각자 개별적으로 보유
  • light-weight process : 쓰레드의 다른 이름 -> 쓰레드들은 프로세스에 있는 메모리 space를 공유해서 사용하기 때문
  • 일반적인 프로세스들은 쓰레드가 1개 밖에 없음

A single-threaded vs. a multi-thread processes

  • single-threaded

    • data : address space에 data를 저장하는 부분
    • file descripter : 파일을 open한 descripter
    • register : cpu상태를 나타내는 register값들
    • stack : function을 어떤식으로 호출해 갔는지
    • code : code의 어떤 부분을 실행하는 thread
  • multi-thread

 

    • 쓰레드가 동시에 실행 중
    • code의 여러부분을 동시에 실행 가능
    • 각각이 cpu의 프로그램 counter를 가지고 있어야하고 stack또한 따로 가지고 있어야 함
    • global한 data와 files는 공유
  • 구체적인 memory map

  • 왼쪽은 일반적인 프로세스의 메모리영역
  • kernel 영역과 user 영역(text, data, Heap, stack이 딱 하나!!!)
  • 오른쪽은 멀티 쓰레드의 메모리영역 -> kernel은 동일
  • data그대로 Heap그대로 / stack이 따로따로 존재 -> 쓰레드마다 각기 다른 부분을 호출하며 수행하기 때문에 각 쓰레드별로 어떤 식으로 호출했는지 stack을 따로 잡아주어야 함

왜 여러개의 프로세스를 사용하지 않고 병렬적인 쓰레드를 사용하는가

  • process
    • fork() : 오버헤드가 큼 - time & memory -> 새로운 프로세스를 생성할 때 매우 많은 시간과 메모리가 필요
    • 각각 독립적인 space를 가지고 있기 때문에 데이터를 주고받기 어려움 -> IPC라는 interprocess comunication mechanism을 사용해야 가능
  • Threads
    • stack영역을 하나 더 추가하는 것이기 때문에 매우 빠름, 하나의 memory space를 공유하기 때문에 메모리 사용량도 적음 
    • data를 공유하기 쉬움(global, static) -> 한 쓰레드가 사용한 것을 다른 쓰레드가 가져다 사용 가능

Multi-threading

  • 쓰레드들이 각각 일정 부분의 일들을 나누어서 처리
  • cpu core들을 여러개 가지고 있는 경우 각각의 cpu에 쓰레드들이 수행하도록 함으로써 병렬로 처리되도록 할 수 있음

  • cpu가 하나만 있는 경우 하나의 cpu에서 여러 쓰레드가 time sharing을 하기 때문에 수행 속도가 빨라지거나 하지 않음
  • 여러 cpu가 있는 경우(core), 각각의 cpu에서 쓰레드를 나누어 수행하는 경우 time sharing을 할 필요가 없어지기 때문에 진정한 병렬처리를 수행 할 수 있게됨 -> 속도가 빨라짐

POSIX threads

  • Pthreads라고 표현
  • Unix계열의 운영체제들에서 multi threading을 하기위한 프로그래밍 API들을 define해놓은 것
  • pthread API
    • 쓰레드 관리 : 생성, 종료 등/ 쓰레드의 속성 결정
    • 데이터 공유할때 어떻게 충돌하지 않도록 공유하는지 -> mutexes, condition variables

  1. pthread_self : 쓰레드의 ID를 가져옴
  2. pthread_equal : 두 쓰레드가 같은 쓰레드인지 비교
  3. pthread_create : 가장 많이 사용, 쓰레드를 생성
  4. pthread_join : 쓰레드가 종료 될 때까지 기다림
  5. pthread_detach : 쓰레드를 Detach
  6. pthread_exit : 쓰레드를 종료시킴

Using Pthreads

  1. 어떤 부분을 thread로 만들 것인가
  2. 함수의 형태로 만듦
  3. pthread_create의 함수의 인자로 넘겨줌
  • libpthread library를 써줘야 컴파일 가능
    • gcc -lpthread -o outputfile threaded_program.c
  • 프로그램 실행 시 생성한 쓰레드들이 동시에 실행 됨 -> 데이터 공간 공유(address space)

pthread_create(3)

 

  • void * (*start_routine) (void *) : 쓰레드를 만들고 싶은 함수의 function pointer를 받아서 argument로 void 포인터를 받고 리턴 값도 동일
  • void *arg : 쓰레드를 수행할 함수에 넘겨줄 argument를 정해줌
  • const pthread_attr_t *attr : 자세히 사용하지 않을 경우 NULL로 세팅
  • pthread_t *thread : 생성하면 handle을 return -> 이것을 가지고 특정한 쓰레드를 지정할 수 있음
  • 성공한 경우 0을, 실패한 경우 -1을 return -> 반드시 확인해야 함!!
    • EAGAIN : 충분한 리소스가 없음 -> ex) 스택을 새로 할당해야 하는데 무한대로 쓰레드를 만들수는 없음(스택이 모자람)

    • pthread_t objects

      • 쓰레드를 지정하기 위해 쓰레드의 ID가 pthread_t 타입으로 return됨

      • 어떤 타입인지 알 필요 없음 -> 내부적으로 구현이 되어있는지 몰라도 됨

      • 보조함수 : Thread IDs

      1. pthread_self : pthread_t 타입, 자기자신의 thread ID를 리턴

      2. pthread_equal : 두 쓰레드가 같은 쓰레드인지 비교

    • Function started by pthread_create(3)

    • argument로 void pointer를 받고 리턴값으로 void pointer로 넘겨줌 -> 어떠한 데이터에 대한 포인터도 넘겨줄 수 있다는 의미 -> type casting을 하여 사용

    • 리턴값 또한 사용할 때 type casting을 하여 사용

    • 예제

     

    1. pthread.h를 include
    2. pthread_t의 핸들을 선언
    3. attribute는 NULL, 쓰레드는 snow라는 함수 수행, data(string)
    4. (char *)data : type casting을 함
    5. snow가 수행되는 동안 main 쓰레드도 수행 됨
    • fork()와 비교

    • 완전히 동일한 프로세스가 생성됨 -> 오래 걸림

    • 메인 쓰레드가 수행되고 있는 상태에서 새로운 쓰레드를 만들면 Stack만 새로 만들어지고 나머지 부분은 공통으로 사용
    • 각각의 thread를 위한 스택이 존재

    쓰레드의 종료

     

    1. return으로 종료 됨, thread를 pthread_exit라는 함수를 불러서 명시적으로 종료시킴
    2. pthread_exit() : 종료되기를 기다리고 있는 어떤 쓰레드에게 retval값을 넘겨줄 수 있음 -> pthread_exit()를 하게 되면 프로그램 자체가 종료 되는 것이 아니라 다른 쓰레드들은 수행중임
    3. exit()는 프로세스 자체가 종료되므로 모든 쓰레드가 종료됨
    4. pthread_cancle()
    • 종료하면서 넘겨주는 return값을 기다리는 함수 : pthread_join(3)

    • thread가 종료되기를 기다림
    • **retval : retrun값을 가져옴 -> 가져올 필요가 없다면 NULL로 설정
    • 여러개가 종료되기를 기다리기 위해서는 각 쓰레드마다 join을 해야함
    • 예제

    1. main thread가 수행되고 있다가 func이라는 함수를 수행하는 쓰레드가 생성됨
    2. pthread_join이라는 함수를 부름 -> mythread가 종료될 때 까지 기다림
    3. func가 만약 (void *)42를 return했다면 그 값을 i에 받아서 사용할 수 있음
    • pthread_detach(3)

     

    • 한 쓰레드가 종료했을 때 exit값을 받아가야 종료된 쓰레드에 관련된 자원들이 릴리즈 됨
    • 그러나 쓰레드를 종료하는 것을 기다리지 않겠다고 선언하는 함수가 pthread_detach라는 함수
    • thread에 종료될 thread를 넣으면 이 thread가 종료 될 때 기다리지 않고 바로 종료한다는 뜻

    1. 여러 쓰레드 생성 -> create
    2. 각 쓰레드가 종료 될 때마다 기다려서 return값을 가져옴

    1. 각 쓰레드를 기다리지 않음 -> pthread_detach()

     

     

     

     

     

     

    Signal Conceps

    • 프로그램 수행 중 여러가지 이벤트가 발생
      • floating point error
      • 파워가 꺼지기 직전 경고
      • 알람기능
      • child process가 종료됐을 경우 parent로 알림
      • Ctrl + C : 정해진 명령
      • Ctrl + Z : 중단
    • 이러한 이벤트들을 signal이라고 부름
    • 비동기적인 이벤트를 프로세스에 알려줌 - ex) Ctrl + C를 누를 경우 알림을 줘야하는데 언제 주는지는 정확히 모름
    • Signal 발생시 프로세스에게 전달이 되고 다양한 액션을 취함
      1. 무시
      2. signal을 catch : 적합한 함수를 호출해줌 -> signal handler
      3. 함수를 명시적으로 등록하지 않았더라도 default된 액션을 취함
        1. 무시
        2. 프로세스 종료
        3. 프로세스 종료 후 core dump라는 파일(상태값 - 레지스터 값, 메모리의 값)을 만듦 -> 프로그램을 디버그할 때 사용가능
        4. 프로세스를 중지(일시정지)시킴
        5. 만약 정지되어 있다면 다시 수행을 재개

      Linux Signals

       

      • signal.h라는 헤더파일에 int형으로 저장되어 있으나 Name형식으로 정의되어 있음
        • SIGALRM : 알람을 울려줌 -> 14번, default는 term(terminated - 프로세스 종료)
        • SIGCHLD : child process가 종료 되었을 때 부모 프로세스가 받는 것 -> default는 무시
        • SIGCONT : 프로세스가 중단되어 있을 때 이 시그널을 받으면 다시 재개 -> default는 cont(재개)
        • SIGKILL : 이 시그널을 받으면 반드시 죽음
        • SIGPWR : 시스템의 파워가 곧 꺼질 것이라는 알림
        • SIGSEGV : 잘못된 메모리의 접근 시 알림 -> default는 core(특정 상태를 파일에 dump) - 이 파일로 분석 가능
        • SIGSTOP : Ctrl + Z와 같은 기능 -> 프로세스를 잠시 정지
        • SIGTERM : process termination -> SIGKILL과는 다르게 signal handler로 처리가능
        • signal(2)/signal(7)에 많은 정보가 있음

      Dispatching Signals: kill(2) and raise(3)

      • signal을 특정 프로세스에 보낸다는 의미
      • kill(2) and raise(3)

      • kill(2)
        1. pid라는 프로세스에게 지정한 signo을 보내는 것 - ex) pid = 100이라는 프로세스에 SIGTERM을 보냄
        2. pid > 0 : 해당하는 pid에 시그널을 보냄
        3. pid = 0 : 0으로 설정하면 signal을 보내는 프로세스와 같은 그룹ID를 가지는 모든 프로세스에게 지정한 signal을 보냄 -> broadcast, multi-cast
        4. pid<-1,pid = -1
          1. pid 가 -1 이면, 1번 프로세스를 제외한 모든 프로세스에서 시그널을 보냄
          2. pid 가 -1 보다 작으면, -pid 프로세스가 포함된 모든 그룹의 프로세스에게 시그널을 보냄
        • raise(3)
          1. kill(getpid(),signo)와 같은 역할 -> 자기 자신에게 시그널을 보냄

        signal delivery and handler execution

         

        • 프로그램이 수행중에 언제 외부의 signal이 도착할지 알 수 없음
        • 프로그램이 실행되는 중간에 signal이 도착하면 프로그램은 잠시 수행을 중단하고 signal handler라는 함수에 감
        • signal에 해당하는 일을 처리 후 return
        • signal을 받았던 장소로 돌아와서 명령 수행
        • 어느 시점에 오는지 정확히 모르므로 비동기적인 처리를 해야함

        setting up a Signal Handler: signal(2)

        • signal handler를 등록하는 방법
        • signal(2)

        • signum - 지정한 시그널 번호/handler - handler 등록 -> type은 지정한 함수에 대한 fucntion pointer
        • sighandler_t : return값 -> 예전에 이미 등록된 함수 handler의 function pointer를 반환
        • SIG_IGN : 지정한 시그널을 무시
        • SIG_DFL : default action
        • 위 둘 다 아니라면 명시적으로 signal handler를 등록
        • 요즘에는 signal(2)이라는 함수보다 sigaction(2)이라는 함수를 많이 사용
        • 예제

        1. SIGINT : signum/ ouch : 함수 -> 지정

        2. 1초 마다 반복

        3. 만약 SIGINT가 Ctrl + C라면 프로그램 실행 중 Ctrl + C를 하면 ouch 함수를 수행


        sigaction(2)

        • signal함수와 같은 역할이나 좀 더 정밀한 작업에 사용됨 -> thread나 real-time signal 등

        • signum에 해당하는 sigaction이라는 structer에 저장

        1. 첫 번째가 handler부분 - signal이 발생했을 때 실행
        2. sa_mask : signal이 동작하는 동안에 이곳에 저장된 signal들이 오면 막아줌
        • 현재 리눅스에서는 sigaction으로 구현되어 있으므로 간단한 경우를 제외하고는 이 시스템 콜을 사용할 것

        signal sets

        • 여러 시그널을 한 세트로 지정

        • sigemptyset : set에 대한 pointer를 주면 비워버림
        • sigfillset : 전체 집합으로 설정 - 전부 1로
        • sigaddset : 지정된 signum을 추가
        • sigdelset : 지정된 signum을 제거
        • sigismember : signum이 포함되어 있는지 알려줌
        • 예제

        1. sigaction을 사용
        2. sigemptyset으로 비움
        3. flag를 0또는 NULL으로 지정
        4. ouch라는 handler등록
        5. SIGINT가 왔을 때 sa에 지정된 handler를 수행
        6. sa_mask를 비웠기 때문에 아무런 signal도 막지 않고 받아들인다는 뜻
        7. sigaction의 세 번째 파라미터는 입력된 handler가 등록이 되어있다 하면 예전의 handler를 리턴
        8. while로 반복하며 Ctrl + C를 누르면 지정한 핸들러가 호출됨

        Signal Mask

        • 시그널이 오는 것을 막을 수 있음

        • how에 대한 action을 취함/set
          1. SIG_BLOCK : set에 지정한 시그널들을 이미 블록된 signal들에 합산하여 막아줌
          2. SIG_UNBLOCK : set에 지정된 시그널들은 signal이 전달 될 수 있도록 설정
          3. SIG_SETMASK : set에 지정된 시그널들을 block시켜줌
        • sigpending : 시그널이 block되어 대기하는 상태 -> 이러한 시그널들이 무엇이 있는지 파악하여 set에 반환
        • 예제

        1. block_set을 비움
        2. SIGINT를 추가 -> 비운 후 추가 했으므로 SIGINT만 설정
        3. sigprocmask : 현재 blocking되어있는 signal set에 block_set을 추가/이전에 지정된 signal set들을 prev_set에 리턴
        4. 아래 함수가 수행되는동안 Ctrl+C를 눌러도 block되어 (pending)대기 -> 여러번 누르더라도 1개만 저장 됨
        5. sigprocmask : 이전 blocking set들을 다시 되돌려 놓음 -> SIGINT가 없을 때의 상태
        6. 이 후 바로 이전에 눌렸던 pending된 signal이 바로 전달되어 default action인 term을 실행
        7. for루프에 가기전에 프로세스가 끝나버림

         


        Timers, and Sleeping

        • 일정 시간이 경과하면 알림

        • second만큼 지나면 SIGALRM이 프로세스에 도착
        • 만약 seconds=0이면 setting해놓은 알람을 canceled
        • 알람은 프로세스에서 딱 1개만 설정
        • POSIX interval timers가 Unix에 추가됨 -> 여러개의 timer setting가능
        • 예제

        1. handler등록
        2. sigaction : SIGALRM에 handler 등록
        3. alarm(5) : 5초로 등록
        4. 반복문이 진행되다가 5초가 지나면 지정된 함수를 실행
        5. waiting = 0이므로 반복문 종료

        sleep(3), nanosleep(2)

        • 지정된 시간동안 프로그램 수행을 중단시킴

        • seconds동안 정지
        • msec, nsec로 정밀하게 조정가능 : nanosleep -> timespec을 받음

        1. tv_sec은 second, tv_nsec은 nanosecond
        • 두 번째 파라미터 rem : sleep중 인터럽트를 받아 종료 되었을 때, 남은 초를 반환 

         

         

         

         

         

         

         

        프로세스의 개념

        • 프로그램이 실행되고 있는 상태(CPU)
        • 2가지 key abstractions
          • Logical control flow : 혼자 cpu를 독자적으로 쓰는 것과 같은 느낌을 줌 -> 그러나 동시에 여러가지 프로세스가 실행중임
            1. 빠르게 스위칭을 하여 multitasking -> 여러 프로세스 실행
          • private viretual addess space : 시스템에 있는 메인 메모리를 프로그램 혼자 전부 사용하는 것과 같은 효과
            1. CPU마다 virtual memory라는 가상메모리를 제공
            2. 프로세스가 스위칭 될 때마다 예전의 프로세스의 내용을 디스크에 저장하고 새로운 프로세스를 읽어옴

        Context Switching

        • 수십, 수백개의 프로세스들이 각자 CPU하나를 독자적으로 사용하고 있다고 느끼게 함

        1. CPU의 코어가 1개라고 가정
        2. process는 A와 B 두 개
        3. processA가 10ms가 진행된 후 process B로 switching시켜줌 -> kernel의 역할 : A의 레지스터값과 메모리의 상태들을 저장하고 B가 수행되기 위해 필요한 값들을 복원
        4. processB 실행
        5. 10ms후에 A로 스위칭 -> 레지스터값과 메모리 상태를 저장 후 A로 옮기고 A의 정보를 불러옴
        6. 반복 -> 짧은 시간동안 많은 switching이 일어나기 때문에 1개의 cpu를 사용하고 있는 것 같은 착각을 하게됨

        Process States

        1. Idle : 처음에 생성되어 cpu에 할당, 준비 안된상태
        2. Runnable : cpu에서 돌아가지는 않지만 cpu에서 돌아갈 준비가 다 되었다는 상태
        3. Running : cpu를 할당받아 cpu상에서 돌아가는 상태
        4. Sleeping : 특정한 이벤트가 일어날때 까지 기다리는 상태 -> ex)디스크에서 값을 읽어 오는 경우 불러오는 시간동안 sleeping -> 디스크에서 값을 읽어오면 Runnable상태로 / 타이머의 경우도 비슷함
        5. 돌아가는 상태의 프로세스에서 중단 Signal을 받게되면 프로세스를 중단시킬 수 있음 ->  다시 실행 Signal을 받으면 실행됨 - ex) ctrl + c
        6. 수행이 모두 끝나고 나면 Exit로 종료 -> 끝나기 전에 Zombified라는 상태가 될 수 있음 - 부모프로세스가 종료된 프로세스의 뒷처리를 해야함 이것이 완벽하게 안끝난 상태

        Process의 구성

        • 크게 5개의 부분으로 나뉨
        • a code area : 프로그램이 메모리에 로드되어 실행되는 것 -> 프로그램의 코드가 필요, 이것이 메모리에 저장된 것
        • a data area : 변수들이나 melloc으로 할당된 메모리들
        • a stack area : 함수를 호출할 때, stack에 데이터들이 쌓임
        • a user area : 해당 프로세스에대한 정보들을 가지고 있음
        • page tables : 사용하고 있는 메모리에대한 정보를 가지고 있음
        • 예제 : x86에서 돌아가는 리눅스의 메모리 맵 - 프로세스

        • 0번부터 시작하여 총 4GB : 1GB는 kernel에서 맵핑되어 사용되고 3GB는 사용자 프로세스에 의해 사용됨
        • 위 그림은 하나의 프로세스에 대한 메모리 맵
        • stack, code, data(heap - malloc) 등 저장되어있음
        • 4GB가 1개의 프로세스
        • 만양 100개의 프로세스라면 400GB의 메모리가 필요한 셈 -> 가상메모리를 사용
        • 가상메모리 : 스위칭 되는 시점에서 사용됨

        Process Table

        • 커널은 어떤 프로세스가 시스템에서 돌아가고 있는지에 대한 총괄적인 정보 저장
        • 각각의 프로세스마다 테이블의 엔트리 차지
          • 프로세스의 부모프로세스 ID
          • 프로세스를 소유한 user나 group
          • 프로세스의 현재 상태(6가지)
          • code나 data, stack, user area들이 어디에 저장되어 있는지
          • pending된 signal들

        1. 각각의 프로세스에 대한 entry
        2. PID(프로세스 ID), PPID(부모 process ID), Stat 등에 대한 정보들 저장
        3. process들을 스위칭 시키며 스케줄링 -> 순서를 정함

        Unix/Linux의 process관리

        • 처음에 부팅시 1개의 process만 존재 - Init(sbin/init) -> PID는 1
        • 프로세스를 만듦 - 기존에 있는 프로세스를 복제하는 방식 -> init(parent process)에서 복제(child process -> init에서 만들었으므로 PPID = 1)
        • 그러므로 init은 모든 프로세스들의 조상프로세스
        • 프로세스들은 fork()라는 시스템콜로 복제 -> parent process와 child process는 완전히 동일(PIDs, PPIDs만 다름) - ex) code, data, stack을 그대로 카피
        • 예를들어 init이 fork로 init을 만들었다면? -> 이 자식프로세스가 프로그램 파일을 가지고 어떤 작업을 수행하길 원함
        • code를 원하는 실행파일의 코드로 바꿔줌 -> execve() 시스템 콜 이용 -> 원하는 프로세스가 만들어짐

        • fork()를 통해 여러 process를 만들어 놓음 -> 그 후 exec(getty) (= getty라는 프로세스 실행)와 같이 쓰면 프로세스의 code가 바뀜
        • getty : login을 handling -> 로그인 메시지를 처리
        • login후 fork(bash) -> bash를 실행하는 자식프로세스로 변경됨
        • shell prompt에서 명령어를 입력하여 프로세스를 실행시키는 과정
          1. 콘솔이 깜빡임 : shell process가 수행이 되고 있는 상태
          2. 실행파일의 이름을 입력하고 엔터
          3. shell process자체를 복제시킴 - fork이용
          4. 원하는 명령어를 수행하기위해 코드부분을 우리가 지정한 실행파일의 코드로 대치 -> exec()
          5. 실행파일의 코드를 수행하는 자식 프로세스가 생성됨
          6. shell process는 자식프로세스가 명령어를 수행하고 끝나기를 기다림 -> wait()
          7. 자식 프로세스가 종료되면 부모 프로세스가 깨어나서 뒷처리 후 다음 shell prompt를 화면에 표시

         

        1. A라는 shell process(parent)가 있다고 가정
        2. 실행파일 입력 후 엔터
        3. fork()실행
        4. process 복제(child)
        5. execv라는 시스템 콜을 실행
        6. 코드영역이 B로 바뀌면서 다른 process가 됨
        7. 수행 후 종료 -> exit()
        8. parent process는 자식프로세스가 진행되는 동안에도 같이 실행될 수 있음 -> wait()로 기다림
        9. child process가 끝나면 대기하고 있던 parent process가 수행을 재기 -> prompt 깜빡거림

        Process 관리 시스템 콜

        • getpid : 프로세스 ID를 가져오기 위함
        • getppid : 부모 프로세스의 ID를 가져옴
        • fork : 자신을 복제하여 자식 프로세스를 만들기 위함
        • exec : 코드부분을 바꿔서 프로그램을 실행
        • exit : 프로세스를 종료하면서 상태변수 전달
        • wait : 자식프로세스가 끝날때까지 기다리는 역할
        • getpid(2), getppid(2)

         

          • getpid : process ID를 받아오는 함수 : 양의 정수형태, 아무도 사용하지 않는 번호가 있으면 사용
          • getpptid : parent process의 process ID를 받아오는 함수
          • init process : process ID가 항상 1
        • fork(2)

         

        • 자기자신을 복제하여 새로운 프로세스를 만듦
        • 두 프로세스는 완전히 똑같은 프로세스 -> PID와 PPID만 다름
        • pid_t 타입을 return - return을 두 번하는 시스템 콜
        • parent process는 child process의 PID를 child process는 0이라는 값을 return값으로 받음
        • 예제

         

        1. fork()라는 시스템 콜을 불렀을 경우 완전히 똑같은 프로세스를 복사
        2. parent process는 child process의 PID가 리턴, child process는 0이 리턴
        3. fork된 시점부터 process는 두 개
        4. parent process는 retrun값이 0이 아니므로 else문으로
        5. child process는 return값이 0이므로 if문으로
        6. 마지막 printf 수행
        • 예제2

        1. 어떤 함수를 수행하는 프로세스가 있음
        2. L0 print
        3. fork하면 동일한 process가 실행됨
        4. L1 print 두 번 -> 부모, 자식 프로세스가 각각 실행
        5. fork 하면 부모 프로세스와 자식 프로세스가 각각 동일한 process를 만듦
        6. L2를 네 번 출력
        7. fork -> 8개의 프로세스
        8. Bye를 8번 출력
        9. 스케줄러가 프로세스들의 순서를 정하기 때문에 우선순위를 주지 않는 이상은 순서를 예측할 수 없음
        • exec(3) family

         

        • execve를 이용하여 만든 라이브러리들이 모여있음
        • fork를 통해서 생성된 프로세스가 코드부분을 새로운 실행파일의 코드로 바꿈
        • parent process와 다른 코드를 실행하게 됨
        • execve(실행시키고 싶은 경로이름, 벡터형태로 인자 전달, 환경변수들을 벡터형태로 전달)
        • v가 붙어있는 exec함수들 : 벡터로써 argument들을 전달
        • l이 붙어있는 exec함수들 :  argument들을 순서대로 전달 - 쓰고 싶은 만큼 씀
        • e가 붙어있는 exec함수들 : 환경변수를 넘겨줄 수 있음
        • p가 붙어있는 exec함수들 : file name을 넘겨주고 경로를 지정된 PATH라는 절대경로에서 찾음
        • 예제

         

        1. fork값이 0이 나온다면(자식 프로세스)
        2. execve(프로그램, argv, 환경변수) 넘겨줌
        3. 리턴값이 0보다 작으면 에러메시지를 띄우고 아니라면 프로그램을 실행
        • exit(3), atexit(3)

        • 정상적으로 종료시킬때 사용하는 시스템 콜
        • 정상적이지 않은 경우는? -> 외부에서 signal을 받아 강제종료하는경우
        • child process가 parent process에게 SIGCHLD라는 signal을 보내면 wait하고 있다가 프로세스의 status를 받아서 처리
        • function포인터 (함수에 대한 포인터) : 함수를 등록해놓으면 exit하기 전에 함수를 호출해줌
        • 예제

        1. cleanup이라는 함수 등록
        2. fork후 exit하기 전 cleanup을 부름
        3. 부모 자식 둘 다 cleanup
        • wait(2), waitpid(2)

        • 자식 프로세스가 종료되기를 기다림
        • 종료된 프로세스가 보내준 status를 가져와서 진행
        • return값은 종료된 프로세스에 대한 ID
        • status value

         

        1. 오른쪽 8bit가 0이면 process가 정상 종료되었다는 의미
        2. status code는 왼쪽의 8bit에 저장
        3. 외부 signal에 의해 종료된 경우는 그 signal의 정보가 오른쪽에 저장
        4. 테스트 매크로를 사용하여 status값을 조작 가능
        • 특정 프로세스를 종료하기까지 기다리는 경우 waitpid(pid, status값, option-종료될 때까지 기다리는 것이 아니라 상태변화 했을 경우 등 여러 옵션들로 지정할 수 있음 -> 정교한 조작 가능)
        • 예제

        1. N번 반복하여 자식 프로세스 생성
        2. 0이 리턴되면 -> child 종료 - exit(100 + i)
        3. for문을 이용하여 parent process들이 wait
        4. 종료된 child process들의 status가 변경되어 전달
        5. WIFEXITED : 정상종료되었는지 정보를 return -> 정상종료시 1 아니면 0
        6. WEXITSTATUS : status코드 중에서 status부분만 추출(앞 8bit)
        7. 종료될 때 까지 대기하고 정상종료 되면 printf 메시지 출력

        Orphan process

        • 자식 프로세스가 실행되는 중 부모 프로세스가 종료?
        • parent process가 없는 child process가 됨 -> 이것이 orphan process
        • PPID = 1로 세팅하여 init프로세스가 입양한 것 처럼 됨
        • exit(staus)할 경우 init프로세스로 전달됨

        Zombie process

        • child process가 exit된 경우 완전히 종료 된 것이 아님 -> parent process가 wait하여 child process의 status를 가져가야 완전히 종료 된 것
        • parent process가 wait를 안한 경우 : 시스템 리소스들은 모두 삭제되지만 완전히 종료는 안됨
        • 이러한 경우를 Zombie process라고 함

        파일시스템의 구조 : 일반 디렉토리들과 파일들을 조직화해서 모아놓은 것

        • 파티션마다 파일 시스템을 설치
        • boot block : 부팅에 사용
        • super-block : 파티션의 정보 : I-node 사이즈, data block 사이즈(1K,2K,3K ...등의 크기, 몇개의 block이 있는지)
        • i-node table : 파일시스템에 존재하는 파일들의 정보
        • data blocks : 파일들의 실제 데이터

        i-nodes

        • i-node table : i-node들이 각각의 파일의 정보를 가지고 있음
          • File type : 파일타입에 대한 정보
          • 소유주, 어떤 그룹에 속해 있는지
          • owner, group, other가 어떻게 접근 허용하는지에 대한 정보
          • time stamps : 언제 마지막으로 접근했는지, 언제 수정됐는지, i-node의 정보가 언제 변경되었는지, 소유권변경 등
          • 하드링크가 얼마나 있는지
          • 파일의 사이즈
          • 몇개의 block들이 할당되었는지
          • 실제 파일의 data block들에 대한 포인터

        • DB : 1K일 경우 14K -> 14개의 블록으로만 할당하면 용량이 부족
        • 블록안에 포인터를 모아놓고 참조하면서 사용
        • 파일 사이즈가 커질수록 참조하는 포인터들이 많아지므로 찾는 시간이 지연되어 오래걸림

        Directory structure for /etc/passwd

        • i-node에 파일이름이 없다면 - /directory data정보에 존재(block)
        • 하나의 i-node를 여러개의 파일들이 가리킬 수 있음 -> 여러 이름으로 하나의 파일을 가리킬 수 있음(link)
        • 데이터 블록이 바로 존재하지 않고 pointer가 계속 이어질 수 있음
        • ex) 위 그림처럼 2번 i-node블록 -> /directory etc -> 7번 i-node블록 -> /etc directory psswd -> 6422 i-node -> /etc/passwd file

        Hard links vs symbolic links

        • Hard links : 하나의 파일에 여러가지 이름(link)가 존재할 수 있음/ i-node에는 파일이름이 저장되어 있지 않음
        • symbolic links(or, soft links) : 데이터가 다른 파일의 이름 - ex) ln -s target name -> name이 target을 pointing하도록 symbolic link를 만들 수 있음
        • 예제

         

        • 61번 i-node를 /home/erena directory가 this라는 포인터로 가리키고 있고 /home/allyn directory가 that이라는 포인터로 동시에 가리키고 있음
        • 309번 i-node를 /home/kiran directory가 other라는 symbolick link로 가리키고 있음 309번 안의 data block에는 /home/erena/this라는 위의 포인터를 가리키는 포인터가 존재 -> type에 symlink라고 표시되어 있음
        • ex) ln -s /home/erena/this /home/kirena/other -> soft link

        Functions for hard/symbolic links

        • int link(const char *oldpath, const char *newpath); - oldpath가 가리키는 경로를 newpath로 똑같이 가리킴
        • int symlink(const char *oldpath, const char *newpath); - oldpath를 newpath로 가리키게 함
        • ssize_t readlink(const char *path, char *buf, size_t bufsiz); - symbolick link가 가리키는 것이 아닌 내용자체를 read해줌
        • int unlink(const char *pathname); - link를 끊음 symlink는 삭제(i-node까지)하고 hard link는 끊어주고 다른 link가 있는 경우 i-node는 삭제되지 않음

        stat(2) family of functions

        • int stat(const char *pathname, struct stat *buf); - buf에 i-node의 정보 저장
        • int fstat(int fd, struct stat *buf); - 파일을 open하여 i-node의 정보 저장 
        • int lstat(const char *pathname, struct stat *buf); - symlink자체의 파일정보를 가져와서 저장

        struct stat
        struct stat {

        • dev_t st_dev;     /* ID of device containing file */  -> ID - block Device의 정보 읽어옴(major,minor)
        • ino_t st_ino;     /* inode number */  -> Index 번호
        • mode_t st_mode;    /* protection */  -> 어떤 파일인지, 접근권한 저장
        • nlink_t st_nlink;   /* number of hard links */ -> 몇개의 파일 이름으로 포인팅 되었는지 
        • uid_t st_uid;     /* user ID of owner */  -> 유저 ID
        • gid_t st_gid;     /* group ID of owner */  -> 그룹 ID
        • dev_t st_rdev;    /* device ID (if special file) */  -> 디바이스파일들의 ID정보(major, minor)
        • off_t st_size;    /* total size, in bytes */  -> 파일 사이즈
        • blksize_t st_blksize; /* blocksize for filesystem I/O */  -> 각각의 블록 사이즈
        • blkcnt_t st_blocks;  /* number of blocks allocated */  -> 블록이 몇 개 있는지
        • time_t st_atime;   /* time of last access */ -> 언제 access 됐는지
        • time_t st_mtime;   /* time of last modification */  -> 언제 수정 됐는지(write,txt수정 등)
        • time_t st_ctime;   /* time of last status change */  -> status change가 언제 되었는지(i-node정보)

        };

        • st_mode

         

        • st_mode에는 regular file인지 directory인지 등 저장된 파일이 어떤 타입인지에 대한 정보가 들어있음
        • file type - 4byte
        • permission - 읽기, 쓰기, 실행 등의 권한도 표시
        • if문에서 S_IFMT라는 constant와 비교를 하거나 S_ISREG(각 타입마다 존재)라는 함수를 사용하여 비교할 수 있음 

        Directory functions

        • DIR *opendir(const char *name); - 디렉토리파일을 엶
        • int closedir(DIR *dirp); - 디렉토리파일을 닫음 
        • struct dirent *readdir(DIR *dirp); - 한 줄씩 정보를 읽어옴
        • void rewinddir(DIR *dirp); - 처음으로 되돌림
        • 마지막에 읽을 것이 없다면 NULL리턴
        • struct에서 file name이 중요 -> 하나하나 읽어와서 처리

        struct dirent {

        ino_t d_ino;       /* inode number */

        off_t d_off;       /* not an offset; see NOTES */

        unsigned short d_reclen;    /* length of this record */

        unsigned char  d_type;      /* type of file; not supported by all filesystem types */

        char           d_name[256]; /* filename */

        };

         


        Other directory functions

        • int mkdir(const char *pathname, mode_t mode); -> 지정하는 모드로 directory를 만듦
        • int rmdir(const char *pathname); -> 삭제
        • int chdir(const char *path); -> 원하는 디렉토리로 이동
        • char *getcwd(char *buf, size_t size); -> 현재 일하고 있는 working 디렉토리 위치를 리턴

         

         

         

         

        유닉스의 여러가지 시스템 객체들은 파일로 존재

        • Disk-based files(디스크상에 있는 파일들) : 실제 실행 파일(/bin/ls), c파일, 오브젝트 파일 등
        • Special files
          • Deivice files  : /dev(디바이스 디렉토리)/tty(입력하는 터미널 - 타이핑, 화면출력 등), /dev/ttyS0(0번 씨리얼 포트 - com1, com2 등/포트에 연결하여 읽고 쓸 수 있음)
          • System information : proc/cpuinfo(시스템의 CPU정보)
          • 이 파일들은 실제로 디스크상에 존재하는 파일들이 아니지만 유닉스에서는 이러한 시스템 객체들도 파일로 표현
          • 네트워크 소켓
          • 파이프(pipes), FIFOs 등도 파일로 표현

        File descriptors

        • 사용자가 파일을 사용하기위해 핸들을 얻어내는 것
        • fd(file descriptor) = open("hello.txt", ...) -> write, read, close 등 모두 fd를 가지고 실행
        • fd가 필요없는 file descriptor

         

        • 0번 fd : 키보드 상에서 입력하는 것이 프로그램으로 전송되는 것
        • 1번 fd : 화면에 출력되는 것
        • 2번 fd : 에러메시지를 출력하는데 사용
        • 위 3가지는 매크로에서 Symbolic한 이름을 사용하는 것이 좋음

         

        1. prog1|prog2|prog3 -> prog1의 output을 prog2의 input으로 prog2의 output을 prog3의 input으로 들어감
        2. prog3의 output이 출력으로 display됨
        3. 에러메시지들은 바로바로 display됨

        A typical sequence of events

         

        1. 파일을 open하여 file descripter를 얻어냄
        2. 그 후 read, write, lseek, close를 사용

        open(2), close(2)

        • synopsis

        • man 2 open으로 얻을 수 있음
        • 헤더파일들 선언
        • open(파일 이름, 플래그, 모드)
        • 성공적으로 open이 되었으면 fd를 return, 실패시 -1을 return
        • mode는 파일이 생성되는 경우에만 필요
        • close를 해주는 것이 좋음 -> 파일의 record rock들이 releases됨
        • 프로세스를 종료를 시키면 묵시적으로 fd들이 close됨
        • flag들 정리
          • O_RDONLY : read only
          • O_WRONLY : write only
          • O_RDWR : read and write
          • O_CREATE : 파일을 open시 존재하지 않는다면 파일을 생성 -> 이 경우에만 mode파라미터가 의미를 가짐
          • O_APPEND : 항상 파일의 제일끝에 wirte를 함(뒤에 계속 붙임)
          • O_EXCL : atomic하게 파일을 생성
          • O_TRUNC : wirteonly나 read write only로 open시 기존에 있던 내용은 지우고 다시 씀
          • O_SYNC : 파일에 write한 것이 디스크상에 바로 저장되는 것이 아니라 buffer cache에 저장 되었다가 나중에 저장되지만 이 플레그를 사용할 시 바로 디스크에 저장한 후 리턴
          • ex) O_RDONLY | O_SYNC : 읽기 전용으로 열어서 physical I/O가 끝날 때 까지 기다림
        • mode

         

         

        •  ex) S_IRUSR | S_IWUSR : User가 read, write 할 수 있음
        • GRP, OTH 모두 권한을 줄 수 있음
        • 숫자로 더하여 0666으로 설정 할 수 있음
        • 예제

         

         

        1. startup이라는 파일을 fd에 read only로 open함
        2. fd가 -1이 리턴되었는지 확인
        3. fd에 mylife라는 파일을 열고 read and write로 설정, 파일이 존재하지 않으면 생성, 파일이 존재한다면 사이즈를 0으로 만듬
        4. mode는 User만 읽고 쓸 수 있음
        5. w.log라는 파일을 fd에 write only로 open, 파일이 존재하지 않으면 생성,파일이 존재한다면 사이즈를 0으로 만듬, write할 때 덮어쓰지 않고 가장 마지막 부분부터 붙여 씀
        6. user만 읽고 쓸 수 있음

        read(2)

        • synopsis

         

        • fd로부터 n바이트를 읽어와서 지정된 버퍼에 저장
        • 10바이트로 정했는데 더 작을 경우 : 가장 끝에 도달한 경우, 터미널로부터 값을 읽어올 경우 line단위로 읽어옴(line이 끝날때는 거기까지만 읽어옴), 소켓에서 값을 읽어올 때 네트워크 상에서 그만큼의 바이트를 한꺼번에 전송하지 못할 경우 네트워크를 기준으로 읽어옴, 읽어오는 도중 인터럽트가 걸릴 경우 그 순간까지만 읽어옴
        • file offset : 읽고 쓰는 위치, 처음에는 가장 앞을 가리키고 있음 -> read를 n바이트 만큼 했다면 offset이 그 다음으로 이동

        write(2)

        • synopsis

         

        • fd에 n바이트 만큼 쓰겠다는 뜻
        • 에러발생 시 -1리턴
        • file offset에 n바이트만큼 write 후 그만큼 offset증가
        • O_APPEND사용 시 offset에 상관없이 파일 가장 뒤에 붙여 씀

        lseek(2)

        • synopsis

        • file offset을 원하는 위치로 이동시켜주는 함수
        • whence : 기준점
        • offset : 기준점으로부터의 위치
        • SEEK_SET : 주어진 파일에서 제일 첫 부분을 기준점으로 받음
        • SEEK_CUR : read write시 현재 위치
        • SEEK_END : 파일의 끝 바로 다음
        • ex) lseek(fd, 0, SEEK_SET) : 제일 처음/ lseek(fd, -1, SEEK_END) : 파일 가장 마지막 ...

         


        Atomicity(원자성)

        • 시스템 콜이 인터럽트당하지 않고 single operation처럼 실행
        • open이라는 시스템 콜도 여러 기계어들의 집합 -> 이 기계어들이 실행되는 도중 인터럽트가 걸린다면? -> 중단 후 돌아와서 실행하는 것이 보통이지만 만약 open에 영향을 줄 수 있는 값이 인터럽트에서 변경된다면 결과값이 달라질 수 있음
        • 따라서 시스템 콜이 원자성을 가져야 결과값이 달라지지 않음

        Race condition

        • process 1, process 2 둘의 수행되는 속도에 따라 결과값이 달라질 수 있음
        • 즉, 여러 process들이 한 자원을 동시에 사용하게 된다면 결과값이 달라짐
        • 원자성은 이를 없애기 위한 특성중 하나

        예제

         

        1. O_CREATE : 파일이 있다면 그냥 open하고 없으면 만들어서 사용하겠다는 옵션
        2. open시 -1이 리턴된다면 -> 파일이 존재하지 않으므로 파일을 만들어서 내가 독자적으로 사용
        3. O_CREATE라는 옵션을 줘서 다시 한 번 open -> exclusive하게 구현(파일이 존재한다면 사용하지 않고 없는 경우에만 사용한다는 의미)
        4. 그러나 이러한 프로세스가 여러개가 동시에 실행된다면

        1. process A와 B가 똑같은 작업을 수행
        2. A가 open을 실행 -> 파일이 없는 것을 확인하고 그 다음 스레드를 실행 -> time sharing을 하기 때문에 중간에 B가 동일한 작업을 수행 ->파일을 만들기 전에 B로 넘어가서 B도 open을 사용, 같은 이름의 파일이 여전히 없다고 판단 -> B는 그 뒤에 파일을 만들기까지 수행 -> A로 넘어감 A또한 B와 동일하게 만들어서 사용 -> 결국 두 프로세스 전부 동시에 같은 파일을 만들고 있는 상황이 됨
        3. 만약 A가 open으로 파일을 생성 후 B가 생성하려 한다면 이 경우에는 A만 실행이 됨 -> 결과값이 상황에 따라 달라짐(Race condition)
        4. O_EXCL이라는 플래그를 사용하면 open을 두 번 쓸 필요도 없고 원자성(Atomic)도 가짐 

        1. 파일에 제일 마지막에 계속 붙여 쓰려는 경우(O_APPEND를 사용하지 않는 경우)
        2. lseek로 SEEK_END로 offset을 옮긴 후 데이터를 써줌
        3. 동작이 제대로 되는가? -> 여러 프로세스가 동작할 경우 역시 결과가 달라질 수 있음
        4. process1이 lseek를 한 상황에서 process2가 write를 해버리면 process1은 그 위에 덮어쓰게 됨
        5. 위 두 if문을 합한 것이 O_APPEND라는 플래그 -> write가 일어나면 무조건 제일 끝에 붙여줌
        6. 결론 : 나눠서 수행 할 경우 문제가 발생할 수 밖에 없음


        pread(2) and pwrite(2)

         

        • lseek, write, read를 합쳐놓음
        • synopsis

         

         

        • 포지셔널 read, write : offset파트가 추가 - 정해진 오프셋으로 이동 후 read 혹은 write함
        • multi - thread 프로그램에서는 매우 유용(race condition이 발생하지 않음)
        • lseek와 read로 pread를 구현

        1. orig이라는 변수에 SEEK_CUR, 즉 현재 offset을 저장
        2. offset을 제일 처음을 기준에서 offset만큼 옮겨주고
        3. 데이터를 읽어옴
        4. 오리지날로 offset 복원
        5. 위 과정을 인터럽트 없이 한번에 실행하면 pread와 같은 기능을 할 수 있음


        readv(2) and writev(2) : vector I/O

         

         

        • readv : 파일내용을 여러 버퍼에 한꺼번에 읽음
        • writev : 여러 버퍼의 내용을 한번에 씀
        • synopsis

         

         

        •  *iov : array의 포인터
        • iovcnt : 몇개의 entry가 있는지 지정
        • 구조

         

        • *iov_base : 버퍼의 포인터
        • iov_len : 버퍼의 사이즈
        • [0], [1], [2]의 세가지로 지정 버퍼와 len이 각자 다르게 저장하여 구성
        • 이후 writev로 넘겨주면 한꺼번에 iov array의 내용을 write해줌
        • write만 가지고 구현하면 3번이나 사용해야 해서 오류가 많아질 수 있음
        • 한 번에 실행시키기 때문에 atomic함 

        '임베디드SW공학' 카테고리의 다른 글

        [10주차]리눅스 시스템 프로그래밍:Processes  (0) 2018.11.19
        [9주차]시스템 콜 파일(2) - 디렉토리  (0) 2018.11.17
        [6주차]Linux/Unix System Programming  (0) 2018.11.01
        [5주차]git  (0) 2018.10.16
        [4주차]make  (0) 2018.10.07

        Linux/Unix kernel

        • Linux / UNIX 운영 체제에서 다음 코드가 포함 된 부분 :
        • 시스템 간 CPU 및 RAM 공유
        • 서비스를 사용할 수 있도록 모든 시스템 콜 제공
        • 주변기기 핸들링

         

        커널은 디스크에서 RAM으로로드되는 프로그램

        • 컴퓨터를 처음 켜면, RAM에 상주 종료시까지 실행

         

        커널 서브 시스템

        • 메모리 관리
        • 프로세스 관리
        • 프로세스 간 통신 (IPC)
        • 입출력(마우스, 키보드 등)
        • 파일 관리

        Typical Linux/Unix structure(구조)

        • Application : vi, nano ...
        • mode : application user mode(User sapce/level)와 여러 하드웨어 동작 mode(Kernel space/level)로 나뉨
        • Kernel Space level : 부팅, 시스템 초기화, easy-to-use interface 제공(여러 개 동시 사용 조절)
        • Portable OS layer : 하드웨어에 독립적 운영(ARM, x86 등 모두 돌일한 시스템 콜 사용/ C, C++ 등 상위 레벨로 작성)
        • Machin - dependent layer : 하드웨어와 직접적인 연결, HW , I/O디바이스 관리, 프로세서 관리, 메모리관리, 커널/유저모드 스위치, C와 같은 상위레벨, assembly와 같은 하위레벨의 언어로도 작성(ARM, x86 등 모두 다른 구성)

        System Call

        • 프로그램이 Linux/Unix kernel과 대화 할 수 있는 인터페이스를 제공
        • 여러 함수들을 모아놓은 것 같음 - Unix/Linux kernel을 직접적으로 호출
        • Unix family tree

         


        POSIX(Portable Operating System Interface)

        • 위의 family들은 System Call들이 약간씩 차이가 있음 -> 호환성이 떨어짐
        • 해결책으로 POSIX라는 표준을 정함
        • POSIX 1 : process, IPC, signal, C library - 함수정의, 파라미터 정의
        • POSIX 1.b : 리얼타임에 관련된 내용 - 우선순위, 스케줄 real-time signal
        • POSIX 1.c : 스레드, 스케줄링
        • POSIX 2 : Shell과 utilities들 정의

        Function calls

        • 하나의 프로세스안의 function()에서 다른 funtion을 불러오는 것 - mode switch가 일어나지 않음

        System calls

        • 프로세스에서 커널에 있는 funtion을 호출 - mode switch : user -> kernel mode
        • process <-> Os(HW acess) -> return 발생

        시스템 콜 expensive(일어나는 순서)

        1. 프로세스 상태 저장
        2. 커널 모드로 스위치(CPU를 커널이 제어)
        3. 파라미터들(매개변수)이 정상동작하는지 검사 - 커널
        4. funtion요청 -> 요청한 기능 실행
        5. 커널의 상태 저장
        6. processor로 전환 mode switch : user mode로
        7. 커널은 발신자에게 CPU제어권 반환

        1. glibc에서 execve라는 function호출
        2. int 0x80 SW 인터럽트 걺 (ARM은 Swi라는 인터럽트를 걺)
        3. 커널모드로 스위치가 일어나 커널로 jump
        4. 시스템 콜 테이블에서 execve위치에서 수행
        5. 커널모드로 실행 후 에러코드 리턴
        6. 유저모드로 스위치 후 glibc에서 리턴, 다시 돌아옴

        각종 시스템 콜들

        • getuide() : User ID를 가져옴
        • fork() : 자식 프로세스 생성
        • exec() : 새로운 프로그램 수행

        시스템콜 확인 : man systemcalls

         

        시스템 콜과 매칭되는 라이브러리 콜이 있는데 이 둘은 동시에 사용하면 안됨

         


        man 명령어를 사용하여 시스템 콜을 어떻게 사용하는지 확인

        ex) man 2 open

         


        시스템 콜들을 카테고리별로 나누면?

         

         

         


        시스템 콜 에러 반환

        • 시스템 호출은 호출이 성공했는지 실패했는지를 나타내는 상태 값을 반환
        • 이 상태 값은 호출이 성공했는지 항상 확인해야함
        • 오류가 발생하면 모든 시스템 호출은 -1을 반환
        • 모든 프로세스는 errno라는 시스템 전역 변수를 가지고 있으며 이는 마지막 시스템 호출 오류의 숫자 코드를 포함
        • perror()이라는 함수는 시스템 호출 오류를 설명하는 라이브러리 함수이고 여기서 error의 의미를 해석 할 수 있음

        #define   EPERM      1   /* Not  owner */

        #define   ENOENT    2   /* No such file or directory */

        #define   ESRCH      3   /* No such process */

        #define   EINSR       4   /* Interrupted System call */

        #define   EIO           5   /* I/O error */

         …

        1. open으로 text읽기 시도

        2. 에러가 나면 fd는 -1이 됨

        3. 에러넘버를 출력하고 perror에서 확인

        '임베디드SW공학' 카테고리의 다른 글

        [9주차]시스템 콜 파일(2) - 디렉토리  (0) 2018.11.17
        [8주차]리눅스시스템 프로그래밍: File I/O  (0) 2018.11.11
        [5주차]git  (0) 2018.10.16
        [4주차]make  (0) 2018.10.07
        [3주차]GCC&library  (0) 2018.10.07

         

        git을 사용하는 이유

        잘 동작하는 코드가 있었는데 이 코들을 수정했음 -> 수정한 코드가 오류가 나서 작동하지 않음 -> 이전코드로 돌아가고 싶은데 백업해 둔 소스가 없다면?

        실수로 중요한 파일을 삭제한 경우?

        하드디스크가 고장나서 소스가 다 없어졌다면?

         

        함께 개발할 경우 누구의 컴퓨터에 공식적인 소스코드를 보관할지 -> 자체적인 서버에 저장가능

        어떤 팀원이 어떤 부분을 작업하고 수정했는지 알 수 있음

        다른 팀원이 소스코드의 가한 변경을 읽고 쓸 수 있는 방법이 있음

        팀원들이 복사한 소스코드에서 같은 부분을 동시에 수정해버린다면

        실수로 중요한 파일들에 문제(삭제,변경)가 생긴 경우 -> 백업 방식

         

        버전 관리 시스템(VCS : version control system) = source code management(SCM) system

        소프트웨어의 파일(소스코드, 문서, 구조)들에 가해진 변경을 추적하여 관리하는 소프트웨어 -> ex)워드의 undo기능

         

        여러종류의 버전관리 시스템 : RCS, CVS, subversion(SVN) <- tortoise SVN

             Git Mercurial

                  모두 오픈소스

         

        역할 : 모든 팀원들이 접근 할 수있는 공유된 소스코드의 카피제공

          현재 버전과 백업 버전을 포함하여, 소스코드의 여러 버전을 관리

          팀원들이 어떠한 변경을 소스코드에 가했는지 알 수 있도록 해줌

          여러 팀원이 같은 소스파일에 가한 변경의 충돌을 관리해줌

          소프트웨어 소스코드뿐만아니라 다른 리소스에도 적용가능 -> 사진, 여러 문서들

         

        보관소(repository) : 파일들을 저장하는 저장소 -> 변경되어온 소프트웨어들이 저장되어 있음

          최근 뿐만아니라 이전 버전도 사용가능

           여러 컴퓨터(사용자들)이 동시에 사용할 수 있음(남이 만든 것)

         

        중앙 집중식

         

        개인

        그룹

        분산형 버전관리 : 서버 뿐만 아니라 다른 컴퓨터끼리도 동기화 가능

        Git

        대부분의 회사들이 사용중(구글, 페이스북 등)

         

        이점 : 빠른 스피드, 단순한 디자인, non-linear development(순서대로 기다리면서 개발하는 것이 아닌 동시에 개발 가능)

           Fully distributed - 최신 코드가 망해도 이전 버전이 master로 적용했다면 돌아 갈 수 있음

           큰 규모의 소프트웨어도 빠르게 처리

         

        Snapshot storage : 파일들에 변형이 있다면(보통 diff라 말함) 변경된 부분만 저장하는 일반 시스템과는 달리 Git은 변경이 된다면 copy하여 소스코드 자체를 통째로 저장하여 빠르게 불러올 수 있음 -> 용량상의 차이도 거의 없음

        Getting Sharted

        소스를 수정하고 저장하는 과정이 3가지로 나뉨

        mdified : 가장 먼저 수정하기 위해 git directory(repository)로부터 copy해 온 것을 작업공간인 working directory에서 수정

         

        staged : 수정한 소스를 임시로 저장하여 실제로 저장하기 전에 한 번 리뷰할 수 있도록 하는 역할 -> buffer같은 역할

         

        committed : 모든 리뷰가 끝나고 정확한 코드입이 확인 되었다면 commit하여 실제 repository에 저장 

         

        Basic Work flow

        clone : 남이 만들 파일 가져옴

        Init : 처음부터 모든 것을 직접작성

        -> modify(수정) -> stage(확인) -> commit(저장)

         

        명령어들은 실습게시판에 따로 게시

         

        Git branching : main line(보통 master)에서 벗어나 따로 별개의 수정 -> 그 후 다시 합치는 Merging을 통해 서로 합칠 수 있음 

        A->B->C 까지 master라는 pointer로 최근의 소스파일을 pointing

         

        C->D->E 부터 별개의 bug123이라는 pointer를 설정하여 master는 그대로 C를 pointing하게 하고 수정을 시작

        -> 진행할 pointer를 정하고 수정해야 함

         

        만약 bug123에서 되돌릴 수 없는 오류가 나버렸다면? -> 삭제하고 C로 다시 돌아갈 수 있음

         

        bug123에서 만족할만한 완벽한 수정이 이루어졌다면? -> merge명령어로 다시 수정된 것들을 master에 합칠 수 있음

         

        합친 후 필요없는 bug123은 제거 가능

         

         

        branch를 나눴는데 master와 bug456 양쪽 모두 수정을 한 경우 두 branch를 합칠 수 있음

         

        서로 다른 함수를(파일을) 수정했다면 쉽게 합쳐지지만, 같은 함수를 따로 수정했을 경우 불가능 -> 이러한 경우 충돌한 부분을 알려줌 그 부분을 사용자가 직접 수정하기만 하면 됨

         

         

         

        '임베디드SW공학' 카테고리의 다른 글

        [8주차]리눅스시스템 프로그래밍: File I/O  (0) 2018.11.11
        [6주차]Linux/Unix System Programming  (0) 2018.11.01
        [4주차]make  (0) 2018.10.07
        [3주차]GCC&library  (0) 2018.10.07
        [2주차]Shell Script Programming  (0) 2018.09.26

        + Recent posts