본문 바로가기
코스웨어/11년 내장형하드웨어

[내장형]이동현_11월4일_일일보고서

by 알 수 없는 사용자 2011. 11. 7.
728x90
반응형

◎Thread(스레드)


▷프로그램이란?
-기계어로 저장되는 실행되기 전의 파일을 말한다.
▷소스파일이란?
-원시파일 즉,기계어로 되기전의 파일
▷fork()는 프로세스를 복사하는 함수이다.
-하나이상의 프로그램을 실행할때 복제시켜서 사용할수 있다. 이것을 멀티프로세스기법이라한다.
-fork()함수의 장점은 일을 분리시켜서 사용한다.
-fork()함수의 단점은 일을 10개 늘려서 하면 용량은 10배로 늘어난다는 점이다.


-프로세스를 반만 사용할 경우에 공간낭비가 커지게 된다.


멀티프로세스의 효율을 높이기 위해 Thread(스레드)를 사용한다.


-스레드는 프로세서 전체를 분리하는게 아니라 일부만 따로 만든다.(함수를 분리)
-분리해주는 함수가  있는데 그 함수에 넣어서 사용한다.

※CreateThread()함수
 CreateThread()함수는 스레드를 생성한후 스레드 핸들을 리턴한다. 스레드 핸들은 파일 디스크립터나 소켓 디스크립터와 비슷한 개념으로, 스레드 관련 데이터 구조체를 간접적으로 참조하는 매개체가 된다. 스레드 핸들을 가지고 있으면 윈도우 API를 이용하여 해당 스레드를 다양한 방식으로 제어할수 있다.


lpThreadAttributes
 SECURITY_ATTRIBUTES 구조체 변수의 주소값을 대입한다. SECURITY_ATTRIBUTES 구조체는 핸들상속과 보안 디스크립터 정보를 전달하는 용도로 사용한다. 대부분의 경우 NULL을 사용하면 된다.
 
dwStackSize
 새로생성할 스레드에 할당되는 스택크기다. 0을 사용하면 디폴트로 1MB가 할당된다. 
 
lpStartAddress
 스레드 함수의 시작주소다.  
 
lpParameter
 스레드 함수에 전달할 인자.

void형 포인터로 32비트 크기의값 하나만 전달할수 있다. 32비트보다 큰값을 전달할 때는 구조체 변수에 값을 넣고 이 구조체의 주소값을 전달하면 된다. 전달할 인자가 없다면 NULL을넣음되겠죠?
 
dwCreationFlags
 스레드 생성을 제어하는 값으로, 0또는 CREATE_SUSPENDED를 사용한다. 0을 사용하면 스레드 생성후 곧바로 실행되고, CREATE_SUSPENDED를 사용하면 스레드 생성은 되지만 ResumeThread()함수를 호출하기전까지는 실행되지않는다.
 
lpThreadId
 DWORD형 변수 주소값으로 이변수에 스레드 ID가 저장된다. 스레드 ID가 필요하지 않다면 윈도우 NT계열(NT/2000/XP/2003)에서는 NULL값을 사용해도 된다. 

※스레드의 반환형은 DWORD 인자는 void *여야 된다.


-fork()는 프로세스가 완전 분리되어있기때문에 접근을 하기위해서는 파이프를 통해서 가능하지만 Thread(스레드)는 파이프가 필요없이 바로 접근이 가능하다.그래서 멀티프로세서는 전역변수에 접근을 할수 없지만, Thread는 전역변수에 접근이 가능하다.
-스레드 자체가 멀티 스레드이다.
-스레드의 단점


스레드A와 스레드 B가 동시에  A는 101의 값을 변수에 대입하고, B는 C값을 받을때, 과연 B는 100인가? 101인가?  무슨 값이 들어올지는 CPU에 의해서 결정되기때문에 무슨값이 들어오는지 모르는것을 동기화 문제가 발생하였다고 한다. 문제점을 해결하기 위해서는 리소스에 접근시 우선순위를 줘야한다. 동시에 여러개를 처리할수 없다.
대표적인 방법으로 Lock을 거는 방법이다.

Lock을 거는 방법도 여러가지가 있는데 세마포어가 있다.

1. 세마포어란?
- 세마포어는 실행 중인 여러 태스크가 동기화 또는 상호배제를 위해 정의된 자원으로 각 태스크가 세마포어를 획득하거나 반환함으로 태스크 간 동기화 및 상호배제를 구현할 수 있게한다. 즉, 세마포어를 획득해야만 태스크가 작업을 할 수 있다.

2. Binary 세마포어 / Conuting 세마포어

 (1) Binary 세마포어 : 세마포어는 전역변수로서 0 혹은 1 값을 가진다. 이는 Critical Section구역에 태스크가 들어가도록 허용할 것인지 아닌지를 결정하게 해주는 것으로 세마포어가 0이면 Critical Section구역이 비어 있음을 뜻하고 1이면 어떤 태스크가 사용중임을 뜻한다.

 (2) Counting 세마포어 : Critical Section에 여러 개의 태스크가 들어가서 작업할 수 있는 경우 Critical Section이 허용할 수 있는 태스크의 갯수를 관리한다. 즉, 10개의 태스크가 Critical Section에 들어갈 수 있다고 가정하면 ( 세마포어의 초기값은 0 ) 태스크가 하나씩 들어갈 때마다 세마포어의 값이 1씩 감소한다. 그래서 -10이 되면 Critical Section이 가득 찼으므로 작업을 완료한 태스크가 다시 나올때까지 어떤 태스크도 들어갈 수 없다.

하지만 여기서도 문제점이 발생할수 있다. 바로 스타베이션이다. 기아현상이라고도 불린다.


-우선순위가 있는 A,B,C 스레드가 있다. A,B,C가 동시에 인터럽트가 발생하였을때, A→B→C순으로 처리를 하게 된다. 하지만 B를 처리하고있는데 다시 A가 인터럽트가 발생하게되면, C를 처리하지 않고, A를 처리하게되고 또 B가 인터럽트가 발생하게 되면 우선순위가 낮은 C는 무한정 대기를 하고있다가 우선순위가 높은 놈이 일이 끝나고 수행을 하지않을때 비로서 C가 일을 하게된다. 이처럼 C는 제대로 밥도 못 먹는다고 해서 스타베이션(기아현상)이라고 한다. 

또다른 문제점은 데드락(교착상태)이다.


프로세스 A가 변수 K를 쓰겠다고 하게되면 변수 K는 바이너리 세마포어에 의해 1의 값이 들어가게된다. S변수는 0인상태에서 B의 프로세스가 사용하겠다고하면 S변수는 1의 값이 된다. 여기서 A가 S의 값이 필요해서 S을 호출하는데 이미 B가 사용하고 있기때문에 A는 대기를 하게 된다. 이상태에서 B도 K의 값이 필요해서 K에게 갔는데 K가 이미 사용중이어서 B도 대기하게 된다. A와B는 서로 교착상태에서 무한정 대기를 한다. 이것을 알고리즘 버그라고 한다.

☆리눅스의 PS명령

-리눅스의 PS명령은 윈도우에서 시스템 등록정보와 똑같은 기능을한다. PS명령을 사용하게되면 현재 실행중인 프로그램을 볼수 있다. Kill PID번호 명령을 내리게 되면 프로그램을 종료시킬수 있다.
 #include<stdio.h>

int
 main()
{
  printf("Hi~~\n");
  getchar();
  return 0;
}




우리는 윈도우에서 Ctrl+Alt+delete키를 눌러서 강제종료를 하는것과 마찬가지로 리눅스에서 PS명령을 사용하여 프로세스의 PID를 알아내서 강제종료시키는 방법도 있다.
※여기서 중요한 사실은 운영체제는 프로세스의 이름으로 관리하는 것이 아니라 번호(PID)로 관리한다. 사용자가 보기 편하게 하기 위해 단지 이름으로 보여줄뿐이다. 윈도우에서는

☆thread소스
 #include "CRC16.h"
DWORD WINAPI Thread_Read(LPVOID);   //스레드로 실행될함수 

HANDLE  hComm;
 unsigned char ucBuff[13]={0x0d,0x00,0x71,0x00
    ,0x1f,0x00,0x00,0x00,0x0A,0x00,0x00,};

int main()
{
  u_char  caString[] = "하이~";
  DWORD  dwWritten;  // double world Written 쓰기 후 실제 쓴 바이트 수 저장 공간
  DCB    sPState;    // struct Port State 시리얼 포트 상태 저장
  COMMTIMEOUTS  cTime;

  DWORD      ThreadID;
  HANDLE  hThread;
  hComm      // handle of Communicaion
          = CreateFile("COM1"
                      , GENERIC_READ | GENERIC_WRITE
                      , 0
                      , NULL
                      , OPEN_EXISTING
                      , FILE_ATTRIBUTE_NORMAL
                      , 0);

  if (INVALID_HANDLE_VALUE == hComm) //에러 체크
  {
    printf("포트 열 수 없음\n");
    return 0;
  }

  if(0 == SetupComm(hComm, 40963072)) //입출력버퍼 4k,3k  버퍼 설정
  {
    printf("버퍼 설정 에러\n");
    CloseHandle(hComm);
    return 0;
  }

  if(0 == PurgeComm(hComm, PURGE_TXABORT | PURGE_TXCLEAR))  //버퍼를 초기화
  {
    printf("버퍼 초기화 에러\n");
    CloseHandle(hComm);
    return 0;
  }

  sPState.DCBlength = sizeof(sPState); //시리얼 포트 정보
  //구조체의 크기정보를 우선적으로 넣어줘야된다.

  if(0 == GetCommState(hComm, &sPState))  //여기서 부터 시리얼 hcomm에 데이터를 spstate에 가져온다.
  {
    printf("시리얼 상태 읽기 에러\n");
    CloseHandle(hComm);
    return 0;
  }

  sPState.BaudRate  = CBR_38400;  // 속도 윈도우는 CBR을 적어줘야된다. 리눅스는 B
  sPState.ByteSize  = 8;      // 바이트 크기
  sPState.Parity    = EVENPARITY;  // 패리티
  sPState.StopBits  = ONESTOPBIT;  // 스톱 비트

  cTime.ReadIntervalTimeout          = MAXDWORD;  // ReadFile  블럭킹
  cTime.ReadTotalTimeoutMultiplier   = 0;         // ReadFile  블럭킹
  cTime.ReadTotalTimeoutConstant     = 0;         // ReadFile  블럭킹
  cTime.WriteTotalTimeoutMultiplier  = 0;         // WriteFile 블럭킹
  cTime.WriteTotalTimeoutConstant    = 0;         // WriteFile 블럭킹
  SetCommTimeouts(hComm, &cTime);

  if (0 == SetCommState(hComm, &sPState))
  {
    printf("시리얼 상태 설정 에러\n");
     CloseHandle(hComm);
     return 0;
   }
 
   hThread = CreateThread(NULL, 0, Thread_Read, NULL, 0&ThreadID);  //스레드 생성

*((unsigned short *)(ucBuff+11)) = CRC16(ucBuff, *ucBuff -2);

   while(1)
   {
    caString[0=getch();
     if(0 == WriteFile(hComm, ucBuff, 13&dwWritten, 0))
  //if (0 == WriteFile(hComm, caString, 1, &dwWritten, 0))
     {
       printf("쓰기 에러\n");
     }
     else
     {
       printf("쓰기 성공\n");
  if(caString[0== 'q')
  {
    break;
  }
    
     }
   }
   
 
   CloseHandle(hComm);
 
   return 0;
 }

DWORD WINAPI Thread_Read(LPVOID NotUse)
{
  //char   tt=0;
  unsigned char cBuff[64= {0,};
  int i;
  DWORD  dwRead;

  while(1)
  {
    Sleep(50);  // CPU 점유율 낯추기
    ReadFile(hComm, cBuff,6,&dwRead,NULL);
    if(0 != dwRead)
    {
      putchar('[');
    for(i=0; i<6; i++)
    {
    printf("%02X ",cBuff[i]);
    }
      putchar(']');
    }
  }
}



sleep(50)함수를 사용하는데 사용이유는 CPU의 사용을 낮추기 위해서 이다.

 #include<stdio.h>

int
 main()
{
  while(1);
  return 0;
}

☆결과

듀얼코어여서 그런지 실행하면 원래 CPU사용이 100%여야되는데 50%가 되는것을 확인할수 있다. 프로그램을 실행하기전에는 CPU사용이 10%미만으로 사용된다.


-0.1초동안에 P1이 10%를 실행했다고 보면 데이터를 전부 다시 쓰는게 아니라 10%수행된 정보를 메모리에 저장해놓고 다시 불렀을땐 eip주소가 10%번지를 가지키고 있기때문에 10%부터 데이터를 실행하게된다. 계속해서  데이터를 모두 읽는 불편한 행동은 하지 않게!!

-P1의 프로세스 처리가 완료되면 다음을 수행한다.



이것이 CPU의 동작과정이다. 연결리스크 방식이다. OS에 있어서 독립적으로 실행가능한 잡(job)의 부분에 컴퓨터시간을 할당하는 루틴 이것을 스케쥴러라고 한다.  위그림은Context switching이다.
 
※스케줄러 + 메모리 관리 + 장치관리를 할수 있으면 운영체제를 만들수 있다.

-sleep(50)은 딜레이를 주는것이 아니라 프로그램을 실행할때 50만큼 사용을 안하겠다는 의미이다. 50의 정확한 시간은 잘모르겠네요!!


728x90