커널 동기화 방법

from Study/System 2009/02/16 14:08 view 20506
  1. 원자적 동작(Atomic Operation)

    • 중단됨이 없이 한번에 실행되는 명령. 원자(Atom)가 더 이상 쪼갤 수 없는 원소를 의미하듯 원자적 동작은 나눠지지 않는 명령등을 의미한다.
  2. 원자적 정수연산, 원자적 비트연산

    • 원자적 정수연산은 특별한 자료구조인 atomic_t를 사용한다.

      1. 원자적인 함수들이 다른 자료형에 잘못 사용되는 것을 방지.
      2. 원자적동작이 엘리어스(alias)가 아닌 실제 메모리 주소를 사용하게 한다.
      3. 컴파일러가 이 자료형으로의 접근을 잘못 최적화하지 않도록 방지.
      4. 아키텍처에 따라 다른 구현을 감춰주는 역할.
    • 커널코드 작성시 모든 아키텍처에서 사용가능한 함수를 확인해 보아야 한다.
  3. 스핀락(Spin Lock)

    • 최대한 하나의 스레드에 의해 잠길 수 있는 락을 말한다.

      1. 만약 어떤 스레드가 이미 잠겨진 스핀락을 다시 잠그려 시도한다면 그 스레드는 루프(Busy Loop)를 돌면서(Spin)락을 잠글 수 있을때 까지 기다린다.
      2. 이러한 스피닝(Spinning)은 동시에 하나 이상의 스레드가 같은 위험구역에 진입하는 것을 방지해 준다.
    • 다른 스레드들을 스핀하도록(프로세스 시간을 소모함) 하므로 두번의 컨텍스트 스위칭 시간보다 짥게 잡고 있도록 하는 것(휴면방식을 사용하는것) 이 중요하다.
    • 프로세서가 하나인 시스템 에서도 인터럽트 핸들러가 공유 데이터를 접근하는 것을 방지하기 위해 인터럽트를 비활성화해야 한다.
    • 실제로 보호가 필요한 것은 코드 자체가 아닌 위험구역 안에 있는 데이터이므로 락을 특정한 데이터와 관련시키는 것이 좋다. "struct foo 는 foo_lock 으로 잠김"
  4. 스핀락과 보톰하프(Bottom Half)

    • 보톰하프는 프로세스 컨텍스트 코드를 선점할 수 있으므로, 만약 어떤 데이터가 보톰하프와 프로세스 컨텍스트간에 공유된다면 락을 사용함과 함께 보톰하프를 비활성화시켜 데이터를 보호해야 한다.

      • 프로세스 컨텍스트란 ? 프로세서 고유 컨텍스트(Processor Specific Context) 프로세스는 시스템의 현재 상태의 총합 으로 생각할 수 있다. 프로세스는 실행될 때마다, 프로세서의 레지스터와 스택 등을 사용 한다. 이것이 프로세스 컨텍스트이며, 프로세스가 중단될 때 CPU 고유의 컨텍스트들은 모두 그 프로세스의 task_struct에 저장되어야 한다. 스케쥴러가 이 프로세스를 다시 시작할 때, 이 컨텍스트는 이 정보로부터 복구된다.
  5.  Reader 와 Writer 스핀락

    • 리스트가 갱신(쓰기)되는 경우에는 다른 코드가 동시에 리스트에 쓰거나 읽지 않도록 해야 한다. 상호배제(Mutual Exclusion)가 필요하다.
    • 리스트를 탐색(읽기)하는 경우에는 다른 코드가 동시에 리스트에 쓰는 것만을 방지하면 된다. 쓰기작업이 없다면 동시에 여러 곳에 탐색이 가능하다.
    • 자료구조의 사용이 Reader/Writer로 확연히 구분되는 경우(생산자/소비자)에는 락킹 메커니즘은 Reader/Wrtier 스핀락을 제공한다.
    1. rwlock_t   mr_rwlock = RW_LOCK_UNLOCKED;
    2. read_lock(&mr_rwlock);
    3. /* 위험 구역(읽기 전용) */
    4. read_unlock(&mr_rwlock);
    5. write_lock(&mr_rwlock);

    6. /* 위험 구역(읽기, 쓰기 가능) */
    7. write_unlock(&mr_rwlock);
    • 대기중인 writer는 모든 Reader가 락을 해제할 때까지 락을 잠그지 못하게 되므로 리더가 상당 수 있는 경우 기아상태가 발생하게 될 수 있다.
  6. 세마포어

    • 락을 잡고 있는 시간이 길어지거나 락을 잡은 채 휴면해야 하는 경우에 사용한다.
    • 동작시나리오

      1. 어떤 태스크가 이미 잠겨진 세마포어를 잠그려 하는 경우, 세마포어는 그 태스크를 대기큐로 삽입하고 해당 태스크를 휴면 상태로 만든다.
      2. 프로세서는 다른 코드를 실행할 수 있게 된다.
      3. 세마포어를 잡고 있는 프로세스가 락을 해제하면, 대기큐에 있는 하나의 태스크가 깨어나서 세마포어를 잡게 된다.
    • 바쁜 루프로 시간을 소모하지 않기 때문에 스핀락보다 더 나은 활용도를 보여주지만 스핀락에 비해 세마포어는 훨씬 많은 부가작업이 필요하다.( 인생은 Trade-Off -_ㅡ;; )
    • 세마포어의 휴면 동작이 주는 의미

      1. 락을 기다리는 태스크들이 휴면하게 되므로 세마포어는 락을 오랫동안 잡게 되는 상황에 적합하다.
      2. 락을 잡고 있는 시간이 짧을 경우 휴면하고 대기큐를 관리하고 다시 깨워주는 추가작업들이 락을 잡고 있는 시간보다 길어질 가능성이 있다.(비효율적.)
      3. 스레드의 실행은 락 경쟁에서 휴면 상태가 될 수 있고 인터럽트 컨텍스트는 스케줄이 불가능하기 때문에 세마포어는 오직 프로세스 컨텍스트에서만 얻을 수 있다.
      4. 세마포어를 잡은채로 휴면할 경우 다른 프로세스가 같은 세마포어를 잡으려 하더라도 데드락에 빠지지 않는다.( 두번째 스레드 역시 휴면되므로 결국 첫번째 스레드가 실행)
      5. 세마포어를 사용할 경우 스핀락을 잡고 있어서는 안되는데, 왜냐하면 세마포어를 잡으려면 휴면해야 하는데 스핀락을 잡고 있는 상태에서는 휴면해서는 안된다.
    • 세마포어 VS 스핀락

      1. 락을 잡고 있는 시간을 근거로 결정을 내려야 한다.
      2. 세마포어는 커널 선점을 비활성화하지 않으므로 세마포어를 잡고 있는 코드는 선점될 수 있다.( 스케줄링에 불리하게 작용하지 않는다. )


         요구사항  추천
         락 부담이 적어야 하는 경우 스핀락
         락 기간이 짧은 경우 스핀락
         락 기간이 긴 경우 세마포어
         인터럽트 핸들러에서 락을 해야 하는 경우 반드시 스핀락(스케줄링)
         락을 소유한 채 휴면해야 하는 경우 반드시 세마포어
    • 세마포어는 동시에 여러 스레드가 같은 락을 잠글 수 있다. 선언시 그 숫자를 지정할 수 있다.

      1. 락의 갯수를 1로 지정하여 하나의 스레드만이 이 락을 잡을 수 있도록 한것을 바이너리 세마포어, 상호배제 관점에서의 뮤텍스라고 부른다.
      2. 카운팅 세마포어는 다수의 스레드가 동시에 같은 위험구역에 진입할 수 있도록 하므로 상호배제를 보장하지 않는다.
    • Reader-Writer 세마포어

      1. 모든 Reader-Writer 세마포어는 뮤텍스이다.
      2. Wirter 가 없는 한 다수의 Reader가 락을 소유할 수 있고 Reader 가 없는 상황에서 오직 하나의 Writer 만 락을 소유할 수 있다.
  7. 완료 변수( Completion Variable )

    • 커널의 두 태스크를 동기화시키기 위해 사용, 한 태스크가 이 변수를 통해 다른 태스크로 이벤트를 알려주는 식으로 동작한다.
    • 세마포어를 대신하여 간단히 사용하기 위해 제공되는 방법이다.
    • complete() 을 호출하여 완료변수에 시그널 되기를 기다리는 대기중인 태스크들을 깨어나도록 하는 방법.
  8. 큰 커널 락(BKL, Big Kernel Lock)

    • 특성

      1. 소유한 상태에서 휴면할 수 있다.
      2. BKL이 잠겨있는 경우 커널 선점은 비활성화 된다.
      3. 재귀적인 락이다.
      4. 오직 프로세스 컨텍스트에서만 사용가능하다.
      5. 이것은 필요악이다. 악마의 현신!!
    1. lock_kernel();
    2. /*
    3.  다른 모든 BKL 사용자들과 동기화되는 위험 지역..
    4.  여기서는 안전하게 휴면할 수 있으며, 휴면할 경우 락이 자동적으로 해제된다. 또 휴면했다가 다시 스케줄링되면 자동적으로 락을 얻는다.
    5. 즉, 어느 경우이든 데드락은 피할 수 있지만, 정말로 여기서 데이터를 보호할 생각이라면 휴면하지 말아야 한다.!!!!
    6. */
    7. unlock_kernel();
  9. seq 락

    • 공유 데이터를 읽거나 쓰기 위한 아주 간단한 메커니즘을 제공.
    • 많은 Reader와 적은 Writer가 있는 경우를 위한 매우 가볍고 확장성이 좋은 락이다.(  Writer를 선호, 다른 Writer가 없을경우 항상 락을 잠글 수 있다. )
  10. 선점의 비활성화

    • 커널이 선점형이므로 새로 실행되는 태스크가 선점된 태스크와 동일한 위험구역에 진입할 수 있다 .그러므로 커널 코드는 스핀락을 사용하여 선점 불가능한 코드 구역을 표시해야 한다.
    •  하나의 변수가 프로세서에 고유한 변수라면 락이 필요치 않게 되지만 프로세서간 선점으로 인해 유사 동시적으로 접근될 수 있다.

      1. preempt_disable();
      2. /* 선점이 비활성화됨 ..*/
      3. preempt_enable();
      • 커널 선점을 비활성하여 한다.
  11. 오더링과 배리어

    • 프로그램 코드에 정의된 순서대로 행해야 하는 경우가 있다.

      • 하드웨어를 가지고 작업할 때는 어떤 읽기 작업이 다른 읽기나 쓰기 작업 이전에 행해져야 한다는 식의 요구조건.
    • 컴파일러와 프로세서 모두가 최적화를 위해 읽기/쓰기(인텔x86예외) 명령의 순서를 마음대로 뒤바꿀수 있기 때문에 별도의 방법이 존재한다.

      • 읽기와 쓰기 명령의 순서를 바꾸는 프로세서들은 명령의 순서 요구조건을 만족시킬 수 있는 기계어 명령을 제공한다.
      • 컴파일러에게 특정 지점에서는 명령어의 순서를 바꾸지 말도록 지시할 수도 있다. 배리어(Barrier) !!
    1. a = 1;
    2. b = 2;

      // 프로세서는 a보다 먼저 b에 새로운 값을 저장할 수도 있다. 컴파일러와 프로세서는 a와 b 사이에 어떠한 관련성이 있음을 알 수 없다.

    3. // a와 b사이에 명백한 연관이 존재하지 않기 때문에 재배열이 발생한다.
    4. a = 1;
    5. b = a;
    6. // a와 b사이에 명확한 데이터 의존성이 존재하기 때문에 재배열하지 않는다. a와 b사이에 명확한 데이터 의존성이 존재.
    •  barrier() 함수를 통해 컴파일러가 로드와 스토어 명령을 최적화하는 것을 방지 할 수도 있다.
  12. 요약

    1. 원자적 연산 : 동기화를 담보하는 가장 단순한 방법.
    2. 스핀락 : 해당 락에 오직 하나의 보유자만이 존재할 수 있는 가벼운 락으로 경쟁이 발생하면 바쁜 대기를 수행한다.
    3. 휴면락 : 세마포어와 완료변수, seq락
    4. 경쟁상태를 유발하지 않으면서 훌륭한 동기화를 보장할 수 있는 커널코드를 작성하자!! 심플하게~!

이 글은 스프링노트에서 작성되었습니다.

커널 동기화 기초

from Study/System 2009/02/15 16:02 view 17832
  1. 커널 동기화 ?

    • 메모리를 공유하는 응용 프로그램은 항상 동시적인 접근으로부터 공유된 자원을 보호해야 한다.
    • 다수의 실행중인 스레드가 동시에 데이터를 변경할 경우 다른 스레드가 변경한 부분을 또 다른 스레드가 잘못 덮어써버릴 가능성이 있다.
    • 멀티프로세싱, 선점형 스케줄링 환경에서는 다양한 동시성 문제가 발생할 수 있다.


  2. Critical Region(위험구역) and Race Condition (경쟁 상태)

    • 위험구역이란 공유된 데이터를 접근하여 조작하는 코드 부분을 가리킨다. 원자적 실행을 보장해야 한다.
    • 경쟁상태란 스레드들이 위험구역에 들어가기 위해 경쟁하는 상태.


  3.  락킹(Locking)

    • 한번에 오직 하나의 스레드만이 공유하는 자료구조를 조작할 수 있다는 것을 확실할 방법.
    • 특별한 구역에서 다른 스레드가 동작하고 있을 때 해당 자료구조를 접근하지 못하게 하는 방법.
    • 락은 동시성을 방지하여 경쟁상태로부터 큐를 보호할 수 있다.

      1. 스레드는 방에 들어서면서 방문을 잠근다. 공유 데이터의 사용이 끝나면 스레드는 자물쇠를 열고 방을 떠난다.
      2. 만약 다른 스레드가 방문에 도착했을 때 문이 잠겨져 있다면, 안에 있는 스레드가 문을 열고 나올 때까지 기다린 후에 방에 들어가게 된다.
      3. 스레드는 락을 잠그고, 락은 데이터를 보호한다.


  4. 동시성

    •  프로그램들이 선점되면서 스케줄링 된다는 사실에서 비롯된다. 어떤 프로세스가 위험지역에 있을 때 비자발적으로 선점돼 버리는 일이 발생할 수 있다.

      • 유사동시성(peudo-concurrency) - 실제로는 동시에 발생하지 않지만 서로 엇갈려서 실행돼 마치 동시에 실행되는 것과 같은 효과가 발생.
      • 진정한동시성(true-concurrency) - 대칭형 멀티프로세싱 시스템에서 두 프로세스는 정확히 가튼 시간에 하나의 위험지역에 진입 할 수 있다.
    • 커널에서의 동시성의 원인

      1. 인터럽트 - 비동기적으로 어느때나 발생하여 현재 실행중인 코드를 중단시킨다.
      2. softirq와 태스크릿 - 커널은 현재 동작중인 코드를 중단하기 위해 거의 언제라도 softirq나 태스크릿을 레이즈하거나 스케줄할 수 있다.
      3. 커널 선점 - 커널도 선점형이므로, 커널에 있는 한 태스크가 다른 태스크를 선점할 수 있다.
      4. 유저공간에서의 휴면과 동기화 - 커널의 태스크는 휴면할 수 있으며 따라서 스케줄러가 새로운 프로세스를 실행하게 된다.
      5. 대칭형 멀티프로세싱 - 2개 이상의 프로세서가 코드를 동시에 실행할 수 있다.
    • 동시성으로 인해 문제가 발생되는 경우 (락이 없다면..) : 경쟁상태가 발생

      1. 커널에서 어떤 자원을 조작하고 있는 도중에 인터럽트가 발생하여 같은 자원에 접근하는 경우.
      2. 커널이 공유 자원을 사용하고 있는 동안 커널 코드가 선점되는 경우.
      3.  위험지역을 실행하는 도중 커널 코드가 휴면하는 경우.
      4. 두 프로세서가 동시에 공유데이터에 접근하는 경우.


  5.  보호영역의 결정

    • 동시에 접근될 수 있는 모든 코드는 보호가 필요로 하므로, 사실 보호가 필요없는 코드를 먼저 찾아내는 법도 있다.

      1. 특정스레드에 로컬한 데이터가 있을 경우.( 로컬자동변수, 스택에서만 존재하는 자료구조들.. )
      2. 특정 태스크에 의해서만 사용되는 데이터.
    • 보호영역의 선정 ( 커널의 거의 모든 전역 데이터와 공유 데이터는 어떠한 형식으로든 동기화. )

      1. 데이터가 전역적인가 ? 다른 스레드가 이 데이터에 접근 가능한가?
      2. 데이터가 프로세스 컨텍스트와 인터럽트 컨텍스트간에 공유되는가?
      3. 데이터를 사용하는 도중 프로세스가 선점되는 경우, 새로 스케줄된 프로세스가 같은 데이터에 접근하는가?
      4. 현재 프로세스가 휴면(or 블록)하는 경우가 있는가? 만약 그렇다면 휴면(블록)시 공유 데이터가 어떤 상태에 놓이게 되는가?
      5. 다른 곳에서 이 데이터가 해제되지 않으려면 어떻게 해야 하는가?
      6. 이 함수가 다른 프로세서에서 호출되면 어떤 일이 벌어지는가?


  6. 데드락(Deadlock)

    • 어떠한 스레드도 더 이상 진행할 수 없는 상태

      • 각 스레드가 어떤 자원을 얻으려 대기하지만 이미 모든 자원의 락이 잡혀있을 때 발생한다. 모든 스레드가 서로에 대해 대기하므로 이미 잡고 있는 자원을 해제할 수도 없다.
    • 셀프 데드락(self-deadlock) 이란  어떤 스레드가 자기 자신이 이미 잡고 있는 락을 다시 얻으려 시도할려고 할때 발생.
    •  죽음의 포옹(deadly embrace) or ABBA 데드락 이란 2개의 스레와 2개의 락이 있는 경우 발생.

      • 스레드1이 락 A를 얻음 / 스레드2가 락 B를 얻음
      • 스레드1이 락 B를 얻으려 함 / 스레드2가 락 A를 얻으려 함
      • 스레드1이 락 B에 대해 대기 / 스레드2가 락 A에 대해 대기
    • 데드락 방지

      1. 중첩된 락은 반드시 같은 순서로 잠겨져야 한다. 죽음의 포옹을 방지.

        • 서로 다른 구조체를 보호하는 cat, dog, fox 라는 3개의 락이 있을 때 어떤 함수가 cat->dog->fox 순서로 접근을 하였다.
        • 다른 모든 함수 역시 같은 순서로 락들을 잠가야 한다.
      2. 기아현상(starvation)을 방지해야 한다. 코드가 종료하는가를 되짚어 봐야 한다. 어떠한 코드도 무한대기가 일어나서는 안된다.!

        • 락이 풀리기를 대기중인 스레드들은 적당한!! 시간에 락을 얻을 수 있어야만 한다.
      3. 같은 락을 두 번 잠그지 않아야 한다.( 리눅스에서는 재귀적 락을 제공하지 하는다. )
      4. 락은 최대한 단순하게 설계해야 한다.


  7. 경쟁과 확장성

    • 현재 사용되고 있는 어떤 락을 다른 스레드가 얻으려 하는 상황을 락경쟁(Lock Contention) 혹은 경쟁이라고 한다.

      • 경쟁이 심한 락일수록 시스템 성능 저하에 더 큰 영향을 미친다.
    • 확장성이란 어떤 시스템이 얼마나 잘 확장될 수 있는가를 가리킨다.
    • 락킹의 세세함(granularity) 이란 그 락이 보호하는 데이터의 크기를 고려하는 것을 의미한다.
    • 정확하지않은(coarse)락은 많은 양의 데이터, 전체 서브시스템의 자료구조를 보호하는 경우이다.

      • 락경쟁이 심할 경우 확장성의 저하로 이어진다.
    • 매우 촘촘한(fine grained)락은 매우 적은 양의 데이터, 큰 구조체에 속한 하나의 항목만을 보호한다.

      • 충분한 락 경쟁이 없는 경우 소모적인 부하로서 작용한다.

이 글은 스프링노트에서 작성되었습니다.

10.19(금) 이론 - ( IPC, IOCP )

from Study/System 2007/10/21 22:07 view 23060
1. IPC( Inter Process Communication )

  1) 사용자 정의 메시지
    - SendMessage 로 메시지를 보낸다. 64비트 만 전달 할 수 있다.
    - 포인터는 가상주소간의 접근이 금지 되어 있으므로 불가능하다.

  2) WM_COPYDATA
    - 내부적으로 MMF 를 쓰기 때문에 결국 포인터가 아닌 MMF 공간의 주소를 사용한다.
 
  3) 공유 메모리
  4) MMF
  5) PIPE
    - 이름 없는 파이프는 상대방이 능동적으로 알 수 없다.
      - 만든놈이 알려줘야 한다. DuplicateHandle
      - 상속을 해주자. SetHandleInformation
   
    - 이름 있는 파이프 : 로컬 내에서만 지원한다.
      - '\\pc이름\PipeMailSlot\이름' 의 형식으로 파이프를 생성 해줘야 한다.
  6) 클립보드
  7) 메일 슬롯
  8) DDE, SOKET, RPC

2. IOCP ( 비동기 I/O : 중첩된 입출력, Overlapped I/O )
 
  - 큰파일을 쓰는 동안 다른 작업을 할 수 있도록 할 수 있다.
  - 작업의 취소가 가능해진다.
  - CreateIoCompletionPort 로 IOCP 를 생성한다.
사용자 삽입 이미지


2007/10/21 - [Study/System] - 10.19(금) 실습 - ( IPC 기법들, IOCP 기초 )
Tag | ,
1. IPC - 이름 없는 파이프

more..



2. IPC - 이름 있는 파이프

more..


3. IPC - 클립보드

more..


4. IPC - WM_COPYDATA

more..


5. APCQ 기본.

more..


6. IOCP 에 대한 단계별 기본 코드

  1) Device Ko 대기 - 쓰기 작업이 끝날 때까지 대기, 누가 끝나는지를 알수가 없다.

more..


  2) Event를 대기로 누가 끝나는지, Cancel 을 할수 있다.

more..



3) CALLBACK 함수를 사용한다.

more..



4) 비동기 요청 스레드와 비동기 대기 스레드를 분린한다.

more..


5) 입출력 완료포트 - 한개의 스레드가 여러개의 비동기 작업을 관리 해준다.!!

more..


Tag | ,
1. API Hooking

  - 직접 호출
    1) DLL내의 함수 위치는 항상 똑같을 수 없다. 주소가 달라진다면 호출이 제대로 되지 않는다.
    2) OS가 호출하는 코드를 바꿔줄 수는 있으나 100군데라면 전부 바꿔야 한다.

  - 간접호출
    1) .idta 에 자기가 사용하는 API의 모든 주소를 가지고 있다.
    2) 0x42,0000 에 메시지 박스 함수의 위치를 가지고 있다가 디레퍼런스 해주면 된다.
    3) 이 주소에 사용자가 만든 임의의 함수 f00() 의 주소를 복사해 넣는다면 재배치(Relocation)을 한다.
    4) 사용자가 만든 임의의 함수 foo() 의 주소를 복사해 넣는다면 API Hooking 을 할 수 있다.
    5) 유저레벨에서 Low 하게 함수를 가로 채는 방법이다.

2. 에러 처리

  - 엑셀의 셀 만들어 보기
    1) 한 셀당 3K를 보관 할 수 있다고 가정해보자.
    2) 먼저 넓은 영역을 통째로 잡고 예약만 한 다음에 쓸려고 할 때 확정을 하는 기법을 하고자 한다.
    3) 셀의 사용유무를 기억하는데만도 n의 수치 즉 셀의 수만큼 의 비트수가 필요하다.
    4) 셀에 무조건 문자열을 쓰고 예외가 발생(즉 셀이 비어있거나,더 쓰고자 할때) 의도적으로 exception을 발생시켜서 셀을 확정 짓는다. 그렇다면 효과적인 메모리 관리(?) 가 가능하다.

2007/10/17 - [Study/System] - 10.17(수) 실습 - ( API Hooking, 예외 )
Tag | ,
1.GetMessageBoxA 의 후킹~ 

more..


2. Debug Help API 를 이용한 후킹~

more..


3. __try, __except 기초

more..


4. C++ throw의 __except

more..


5. STH( Structure Termination Handling ) __try, __finally ( 구조화된 종료 처리 )
  1) 개념

more..


 2) 응용 ( 자원관리를 __finally 에서 담당해준다.!!! )

more..


6. SEH 를 응용해서 메모리 할당 해보기. ( 일명 : Big Shell )

more..


7. API Hooking ( SetTimer를 바꿔치기 해보자 - 미완성 ㅜ_ㅜ )
 1) DLL ( Inject 할 Dll )

more..


2) Inject ( DLL을 Inject 하기.. )

more..


Tag | ,

10.16(화) 이론-2 ( Hook )

from Study/System 2007/10/16 19:28 view 15438
1. Hook
사용자 삽입 이미지

  - CPU는 INT이 1개 이므로 8259를 이용하여 외부신호를 받아온다. 15개의 신호를 받아 올수 있다.
  - 마스터 8259중 하나는 IRQ# : Slave신호를 받아온다.
  - IDT는 OS가 구축 해 놓아서 디바이스 드라이버에 신호를 보내준다.
  - 키보드 핸들러는 PORT상에서 키보드 상태를 얻어서 SHIQ에 넣어준다.

  - USER 영역에서는 SHIQ=>RIT로 보낼때 Low Level Hook 를 하거나
  - RIT => MSGQ에 보낼때 가로 챌수 있다. ( 제일 쉬움 )
  - NProtected 와 같은 보안 프로그램은 커널쪽인 IDT에서 암호화를 해서 내보낸다.
 g_hHook = SetWindowsHookEx( WH_GETMESSAGE,    // 훅의 종료(15가지)
                                               GetMsgProc,            // 훅함수
                                               g_hDll,                     // 훅함수를 가진 DLL핸들(주소)
                                                tid );                       // 훅을 설치할 스레드( 0:Global hook )


  - 훅은 '전역'(모든 윈도우) 의 성격을 지닌다. 또한 GetMessage 단계에서 메시지를 가로 챌 수 있다.
  - 서브클래싱은 특정윈도우에 메시지를 보내는 DispatchMessage 에서 가로 챈다.

GetMessage()
{
   1. SendQ, PostQ, 가상입력 Q순으로 메세지를 조사한다.
   2. 먼저 WH_GETMESSAGE 훅이 설치 되어 있다면 DLL을 Load하고 hook 함수를 호출한다.
   3. 메세지를 가져간다.
}

///////////////////////////////////////////////////////////////////////
// 모든 스레드는 Kernel32.dll에 있는 아래 함수 부터 실행된다.
////////////////////////////////////////////////////////////////////////

BaseThreadStart()
{
 __try
 {
   스레드도 dll을 가지고 있으므로 ThreadEntryPoint를 이함수로 해준다.
   가상주소에 있는 모든 Dll에 대해서 DllMain을 호출해준다.
   그리고 사용자 함수(foo) 호출을 한다.
   해지를 위해선 foo가 종료 되었을때 DllMain을 또 불러준다.
 }
 __except( 1 ) // 예외처리
 {
 }
}

///////////////////////////////////////////////////////////////////////
// FreeLibrary 도 MMF를 열고 해지를 한다.
// LoadLibrary의 원리
//////////////////////////////////////////////////////////////////////

HMODULE WINAPI LoadLibrary( LPCTSTR name )
{
  1. CreateFile 로 파일 생성
  2. MMF를 사용해서 가상주소와 연결( MapViewOfFileEx - PE헤더의 주소로 연결)
  3. if ( DllMain 호출( 주소, DLL_PROCESS_ATTACH, 0(명시적) ) == FALSE )
        MMF 해지, 화일 닫고 return 0;  // 로드를 다시 해지한다. TRUE리턴이 아니라면..
  4. return 주소!!!
}

2007/10/16 - [Study/System] - 10.16(화) 실습 ( 스택,힙, 훅, DLL )
Tag | ,

10.16(화) 이론-1 ( 스택, 힙, DLL)

from Study/System 2007/10/16 16:49 view 16756
1. 스택의 원리
사용자 삽입 이미지


- Page-Guard : 접근시 예외를 발생시키는 속성을 가진다.

- 2번째 페이지 에 접근하면 Page-Guard는 Commit 이 되고 3번째 페이지가 Page-Guard 상태가 된다.

- 마지막 페이지에 도달하면 Reserve 상태가 된다. 이를 벗어 나면 오버플로우 가 발생한다.

- 메모리가 필요할 때 Commit 상태가 되는것이다.

- 메모리의 상태를 알고 싶다면 VirtualQuery, VirtualQueryEx를 쓴다.



char* addr = (char*)0x0;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery( addr, &mbi, sizeof(mbi) );

2. Heap
사용자 삽입 이미지

- 힙은 프로세스당 1개씩 있지만 새롭게 힙공간을 만들어도 된다.
- HeapAlloc, HeapFree 는 기본힙을 생성하고 소멸 해준다.
  1) 확보된 영역을 조금씩 쓰기 위해선 OS가 관리해주는 Heap메모리는
 Heap매니저가 담당해준다.( 소형 메모리 할당 )

  2) 좌측 그림처럼 메모리가 조각화 되어서 메모리 낭비가 발생한다.


- HeapCreate, HeapDestroy 는 새롭게 힙공간을 만들어 준다.
  1) 메모리 조각화 현상을 막을 수 있다. 새로운 힙공간에는 똑같은 크기의 메모리만 써서 메모리 낭비를 막는다.

2) 기본힙은 내부적으로 동기화를 수행하므로 멀티스레드에는 안전하지만 수행속도가 그만큼 느려진다. 단일 스레드 프로그램에선 힙을 하나 생성해서 동기화를 수행치 않게 되면 빨라진다.

사용자 삽입 이미지

- 윈도우즈 환경에서의 메모리 관리 함수들의 계층은 위 그림과 같다.
- 이외에도 GlobalAlloc ( 전역 힙 ), LocalAlloc ( 지역 힙 ) 이 있지만 이는 16bit시절에만 의미가 있고
- 지금은 HeapAlloc 이나 다름이 없다. 하지만 GlobalAlloc 은 유일하게 클립보드에 써줘야 한다.
The global functions are slower than other memory management functions and do not provide as many features. Therefore, new applications should use the heap functions. However, the global functions are still used with DDE, the clipboard functions, and OLE data objects.


3. DLL

  - 프로세스에 로드될때 ( DllMain() 생성,초기를 해준다. )    DLL_PROCESS_ATTACH
  - 프로세스에서 해지될때 ( DllMain() 소멸자 역할을 한다. ) DLL_PROCESS_DETACH
  - 스레드가 생성시 ( DllMain() TLS공간을 만들어 준다. )     DLL_THREAD_ATTACH
  - 스레드가 파괴시 ( DllMain() TLS공간을 없애준다. )         DLL_THREAD_DETACH

   - DllMain은 Serialize 가 된다. 스레드 두개가 동시에 접근 못하도록 구현되어 있다.( 한스레드만 접근 )
BOOL WINAPI DllMain( HANDLE hDll,    // DLL 핸들, 결국 주소
                     DWORD  r,                    // DllMain 이 호출된 이유
                     LPVOID how                 // DLL이 Load된 방식( 0이면 LoadLibrary 사용 )
                    )

  - 동적TLS( 스레드에서 dll을 로드 했을 때 전역변수 문제를 해결한다. strtok 의 static변수 )
사용자 삽입 이미지
  - 멀티스레드 환경을 고려할 때 dll의 전역변수는 동적 TLS를 사용해야 한다.
  - 동적TLS는 사용하지 않는 Index에 메모리를 할당하는 기법으로 모든 스레드는 인덱스를 가지고 메모리에 접근한다. 이때 Index는 프로세스에 할당되어 있다.~
DWORD index = 0; // 동적 TLS index로 사용.
//////////////////////////////////////////////////////////////////////////////////

index = TlsAlloc();    // 동적 TLS에 빈슬롯 할당
buf = (char*)HeapAlloc( GetProcessHeap(), 0, 1000 );
// 주소를 TLS에 보관
TlsSetValue( index, (void*)buf );

TlsFree( index );
////////////////////////////////////////////////////////////////////////////////
// 새로운 스레드가 생성 될 때마다 메모리를 할당해서 Thread-Safe를 보장!!
buf = (char*)HeapAlloc( GetProcessHeap(), 0, 1000 );
TlsSetValue( index, (void*)buf );

buf = (char*) TlsGetValue( index );
HeapFree( GetProcessHeap(), 0, buf );
/////////////////////////////////////////////////////////////////////////////////


2007/10/16 - [Study/System] - 10.16(화) 실습 ( 스택,힙, 훅, DLL )
동적TLS MS문서
Tag | ,

10.16(화) 실습 ( 스택,힙, 훅, DLL )

from Study/System 2007/10/16 15:12 view 18194
1. 스택의 원리

more..


2. 기본힙과 새로운 힙 만들기

more..


more..


3. 동적 TLS를 사용한 DLL ( strtok 문제 해결 )

more..


4. DLL이 로드되는 과정 보기(위예제랑 연동)

more..


5. Hook Dll 만들기

more..


6. Hook 한 메시지 읽어오기( Friend)

more..


Tag | ,

메모리에 관한 용어들

from Study/System 2007/10/16 14:55 view 14116
가상메모리 (Vitrual Memory)

 Vitrual Memory로 말그대로 실재 존재하지는 않지만 메모리 역할을 하는 것을 말하는데 윈도우는 하드 디스크의 일정 영역을 '가상 메모리'로 사용합니다.

스왑 (swap)

 시분할 운영체제에서 어떤 작업의 주기억장치 영역의 이미지를 보조기억장치에 기록하고, 다른 작업의 이미지를 주기억장치에 읽어 넣는 방식

* 주메모리의 한도를 넘는 프로세서를 실행하면 가상메모리(하드디스크의 일부분)에 주메모리가 로드할 데이터들중 일부 이미지들이 보내지어 작업처리량을 높이며 효율적으로 한다는것이 바로 가상메모리와 스왑의 메카니즘이긴하나

 하드디스크와 램사이의 전송속도자체의 차이와 주메모리가 넘겨주었던 데이터이미지들을 하드디스크로부터 다시 받는 이 과정에서 연산제어가 딱 맞아떨어지면 그횟수가 합리적 경제적으로 적어지겠지만 그렇지 못하는 경우가 생길때도 있어 흔히 이럴때 우리가 하드스왑현상으로 작업대기및 지연시간이 생겼다라고 말하는것같습니다.


메모리의 구조와 설명 

사용자 삽입 이미지
  메모리의 구조

* Null-pointer Assignment

  이 지역은 프로그램상의 메모리 접근 오류를 잡아내기 위하여 할당된 영역입니다. 어떤 프로그램이든 이 영역에 읽고 쓰기가 금지되어 있는데 이 지역에 read/write가 행해지면 시스템은 access viloation을 발생시킵니다.
시스템으로부터 메모리를 정상적으로 할당 받지 못할 경우 NULL(0x0)을 리턴받게 되는데 바로 이런 NULL pointer assignment를 방지하기 위한 공간입니다.

* MS-DOS 16-bit Compatibility(win98 only)

 win9x에만 존재하는 4MB의 공간으로 MS-DOS와 win16과의 하위 호환성을 유지하기 위한 공간입니다.
이 공간 또한 read/write가 금지되어야 하는데 MS사에서 기술적인 몇 가지 이유로 이 공간을 보호하지 못했다고 하군요.
 win2000에서는 DOS나 win16 어플리케이션을 자신의 유저 주소 공간에서 실행하기 때문이 이 영역이 없다고합니다.

* User area

 프로세스의 사용자 공간입니다. 다른 프로세스에 의해 간섭받지 않는 고유의 공간으로서 이 공간에 exe와 dll 모듈등 자신들만의 데이타를 담는거라고 합니다.
 특기할 만한 것은 win9x에서는 처음 4MB를 제외한 0x400000부터 0x7FFFFFFF인 반면, win NT는 0x10000부터 0x7FFEFFFF로 역시 처음 2GB에서 64KB를 제외한 부분입니다.

 이는 어떤 프로그램이 실행될 때 메모리 상의 베이스 주소가 다를 수 있다는 것인데, 때문에 프로그램을 만들 때 win NT는 win 9x와 호환성에 주의해야 하는것이더군요.

 win NT에 맞추어 프로그램의 베이스 메모리를 0x10000로 맞추었다가는 win9x에서는 접근이 금지된 지역이므로 실행이 불가능하게 됩니다. (물론 요즘엔 win 9x가 사라져가는 추세라서 그리 큰 영향은 없겠지만..)

* 64KB Off-Limits(win2000 only)

 0x7FFF0000부터 0x7FFFFFFF까지는 win NT에서 처음 64KB의 영역과 마찬가지로 메모리 경계(off-limit)를 나타내는 지역입니다. 커널 영역의 접근을 방지하기 위한 일종의 완충지역이라고 보면 된다고 하는데 어렵군요.

* Shared Memory Mapped File(win98 only)

 win9x에만 존재하는 이 1GB의 영역은 모든 프로세스들이 공통적으로 접근하고 공유할 수 있는 시스템 dll들과 MMF(memory-mapped files)가 적재되는 곳입니다. 3대 시스템 dll이라 불리는 kernel32.dll, user32.dll, gdi32.dll에서 advapi32.dll도 이 영역에 로드되며, win NT에서는 이런 시스템 dll조차 비공유 메모리 영역에 따로 적재하여 사용합니다.
 (이런한 이유로인하여 2000의 보안성이 더 높아 금융권에서도 아직 널리 사용되는건 아닐련지 생각해봅니다.)

* Kernel area

win9x에서는 1GB, win NT에서는 2GB로 예약되어 있는 커널 공간입니다.
스케쥴러, 메모리 관리자, 파일 시스템 코드, 네트워크 코드등의 OS 코드와 디바이스 드라이버 레벨의 모듈이 적재되는 곳이며, 원칙적으로는 이 영역에 대한 read/write가 금지되어 있지만,  win98에서는 이 영역에 대해 접근이 가능합니다.
 유저 어플리케이션에서 이 영역을 손상시킬 수 있다는 점 때문에 win98이 자주 다운되고 불안하다는 비난을 받게 된 이유입니다.




●물리적 메모리와 가상 메모리의 매핑 관계

사용자 삽입 이미지
 
물리적메모리와 가상메모리의 매핑관계

* Page table에는 페이지가 RAM에 존재하는지 paging file에 존재하는지에 대한 정보를 가지고 있습니다.
 옛날 DOS및 윈3.1 시절에는 시스템의 메모리는 그 시스템에 설치된 RAM이 전부였습니다. 만약 시스템에 1MB의 RAM이 설치되어 있다면 커밋할 수 있는 메모리는 1MB가 전부였던 것이지요 .
(예를들어 Dos경험자 분들의 공통적인 눈물겨운 사투 EMM386등을 이용한 메모리 확장(Extened) 노가다)

 그러나 오늘날의 현대적 운영체제들은 대부분 시스템에 설치된 RAM 이외에 디스크 파일을 메모리로 간주할 수 있는 메커니즘을 제공하고 있으며, 이 때 이 메모리로 간주되는 파일을 페이징 파일(paging file)이라고 합니다.
 간단히 말해 자신의 시스템에 256MB의 메모리와 256MB의 페이징 파일을 가지고 있다면 물리적 메모리는 512MB가 되는 것입니다.

● 쓰레드 (thread)

 thread는 process 안에서 실행되는 코드의 실행 흐름이라고 할 수 있습니다.

 쓰레드는 프로세스가 할당한 메모리 영역에서 실행되며 프로세스에 할당된 시스템 자원을 사용하게 되며, 프로세스가 생성되고 초기화될 때마다 운영체제는 기본쓰레드(primary thread)를 생성하게됩니다.

  각 쓰레드는 명령 코드와 함께 CPU 레지스터 상태를 저장하는 context와 2개의 스택 영역이 포함되고, 2개의 스택 영역은 특권 프로세스 모드와 사용자 모드를 위한 스택입니다.

● 스레싱( thrashing)

 한 프로세스의 어떤 쓰레드thread 가 프로세스 주소 공간에 있는 데이타에 접근하려고 할 때 그 데이타가 RAM에 존재하고 있다면 CPU는 프로세스의 가상 주소를 해당 물리 주소에 맵핑하고 데이타에 접근하게 됩니다.  그러나 데이타가 RAM에 없다면 그 데이타가 페이징 파일에 있는지 확인하게 되겠지요.

이 상황을 Page fault라고 합니다. page fault가 발생하면 시스템은 페이징 파일내에 데이타가 존재하는지 확인하고 없다면 access violation을 발생시킵니다.

 
 접근하려는 데이타가 페이징 파일에 있다면 시스템은 현재 RAM에 비어 있는 페이지가 존재하는지 확인하고 존재하지 않는다면 RAM상의 어떤 페이지 중에 한 페이지를 선택해서 이를 해제free하게 됩니다.

 만일 해제하려는 페이지의 데이터가 RAM에서 변경된 적이 있다면 (이를 데이터가 더럽혀졌다(dirty)라고 합니다.)

 해당 페이지의 내용을 페이징 파일에 갱신한 후 해당 RAM 페이지를 해제하는데, 새로 비워진 페이지에 원래 접근하려고 했던 데이터가 존재하는 페이지를 페이징 파일로부터 로딩하게 되고 CPU가 가상 주소를 물리 주소에 맵핑한 후에야 비로소 데이터에 접근하게 되는 것입니다.

 만일 시스템에 RAM이 부족하게 되면 page fault가 빈번하게 발생하게 되고 운영체제는 계속해서 RAM과 하드 디스크의 페이징 파일 사이에서 페이즈를 바꾸는 작업에 대부분의 시간을 소모하게 되겠지요.

  이런 현상을 바로 thrasing이라고 부르는데 가끔 win2000/Xp계열을 사용하다보면 어느 순간에 이유없이 하드 디스크가 버버벅 거리며 돌아가면서 성능이 저하되는 모습을 본적이 있을 것입니다.

 이때가 바로 thrashing이 발생한 경우이며 이쓰레드는 인텔계열로 치자면 CPU자체의 L2캐쉬의 크기와 듀얼코어이냐 싱글코어이냐에 따라 발생하는 쓰레씽이 작업지연시간이 줄어들고 여러개의 쓰레씽들도 처리하는 속도가 차이가 나게됩니다.

여기서 한 시스템에 수많은 프로그램들이 동시에 실행된다면 그 많은 프로그램들의 실행 코드나 데이타들을 페이징 파일에서 할당해야 할 테니 페이징 파일이 대책없이 커지지 않을까 하는 의문을 가질 수 있는데.

 이것을 방지하기 위해 시스템은 프로그램이 실행될 때 실행 파일을 열고 코드와 데이타의 크기를 먼저 확인한 뒤에 실행에 필요한 만큼의 영역을 확보한 후 실행 파일의 이미지 자체를 영역으로 할당합니다. 프로그램의 .exe 실행 파일이나 DLL등의 파일 이미지가 주소 공간의 확보된 영역으로 사용될 때 이를 memory mapped file이라고 합니다.

 (예를들어 윈도우즈에서 실행중인 파일이나 dll을 삭제하려고 할 때 "실행중이므로 삭제할 수 없다"라는 메시지가 뜨면서 삭제가 안되는 경우를 많이 보았을 것입니다. 이는 실행한 파일이나 dll을 전부 메모리에 올리지 않고 필요한 부분만 메모리에 올려지고 나머지는 memory mapped file로 묶여져 있기 때문인 것입니다.)

● 가상메모리 설정법

 가상메모리는 실제 물리적메모리의 1.5~2배정도를 설정해주는것이 좋다는 MS사의 비공식? 사항이 있었던 것으로 기억하며 설정방법은 최소와 최대를 모두 같은값(실제 물리적메모리의 1.5~2배 의 값)으로 설정하는 것을 추천합니다.
그리고 가상메모리공간을 확보할 하드디스크는 운영체제가 설치되어있는 드라이브가 아닌 다른 드라이브로 함으로써 스왑현상을 어느정도 해소하게 하는것이 좋습니다.

예를 들어 비스타 얼티메이트 64비트 사용합니다. 파티션은 100g를 둘로 나눠서 d에 가상메모리 시스템이 관리시키고 위에 모든드라이브에서 윈도우에서 자동관리해놓고 그냥 사용합니다.
1.5배,2배 굳이 지정하지 않구요.

Tag |