프로그래밍을 통한 프로세스의 생성


프로세스 생성 함수



BOOL CreateProcess(

LPCTSTR lpApplicationName ,   //실행파일 이름 지정

LPTSTR lpCommandLine ,          /*매개변수 전달  (실행파일이름과 매개변수                            

                                                     정보를 묶어서 하나로 이곳에 표현 가능)*/

LPSECURITY_ATTRIBUTES lpProcessAttributes ,

LPSECURITY_ATTRIBUTES lpTreadAttributes ,

BOOL binheritHandles ,

DWORD dwCreationFlags ,

LPVOID lpEnvironment ,

LPCTSTR lpCurrentDirectory ,

LPSTARTUPINFO lpstrartupInfo ,

LPPROCESS_INFORMATION lpProcessInformation

);




LPSTARTUPINFO 

 정보전달

CreateProess 

정보반환

  LPPROCESS_INFORMATION 


※CreateProcess함수를 호출하기 위해서는

 LPSTARTUPINFO, LPPROCESS_INFORMATION 구조체를 선언해야한다.


LPSTARTUPINFO

이구조체를 통하여 생성하고자하는 프로세스 특성정보를 설정하여 CreateProcess에 인자로 전달한다.

 

LPPROCESS_INFORMATION 

CreateProcess함수가 생성된 프로세스정보를 LPPROCESS_INFORMATION 에 반환한다.




LPSTARTUPINFO


typedef struct _STARTUPINFO{

DWORD cb;                //구조체 변수의 크기

LPTSTR lpReserved;

LPTSTR lpDesktop;

LPTSTR lpTitle;          //콘솔 윈도우의 타이틀 바 제목

DWORD dwX;            //프로세스 윈도우의 x좌표

DWORD dwY;            //프로세스 윈도우의 y좌표  

DWORD dwXSize;     //프로세스 윈도우의 가로 길이

DWORD dwYSize;     //프로세스 윈도우의 세로 길이

DWORD dwXCountChars;

DWORD dwYCountChars;

DWORD dwFileAttribute;

DWORD dwFlags;    //설정된 멤버의 정보

WORD wShowWindow;

WORD cbReserved2;

LPBYTE lpReserved2;

HANDLE hStdInput;

HANDLE hStdOutput;

HANDLE hStdError;

} STARTUPINFO, *LPSTARTUPINFO;


LPPROCESS_INFORMATION


typedef struct _PROCESS_INFORMATION

{

HANDLE hProcess;        //프로세스의 핸들

HANDLE hThread;         //쓰레드 핸들

DWORD dwprocessId;    //프로세스의 ID

DWORD dwThreadId;      //쓰레드ID

} PROCESS_INFORMATION;

DWORD cb; 를 보면 구조체의 크기인데 왜 구조체가 정해져잇는데 구조체의 크기를 인자로 전달하는 걸까?

CreateProcess 함수를 보면 9번째 인자는 LPSTARTUPINFO로 확정되어있다.

그런데 나중에 다른인자를 올수있게 라이브러리를 개편한다면 전과의 호환성을 위해 구조체를 구별 할 수있게 전달하는 것이다.



예제 5-1]



AdderProcess 자식프로세스 단순덧셈프로세스

CreateProcess 부모프로세스 AdderProcess를 실행시키는 예제이다.


표준 검색경로

1 실행중인 프로세스의 실행파일이 존재하는 디렉터리

2 실행중인 프로세스의 현재 디렉터리(Current Directory)

3 Windows의 시스템디렉터리 (System Directory)

4 Windows 디렉터리 (Windows Directory)

5 환경변수 PATH에 의해 지정되어 있는 디렉터리




실행결과

 







프로세스와 스케줄러의 이해


프로세스의 이해

 

프로세스란?

메인 메모리로 이동하여 실행중인 프로그램 → 일반적인 정의

보통 프로세스라 하면 실행중인 프로그램 이다. 라고 표현을 한다.

틀린 표현은 아니다. 

그러나 메인메모리가 256메가 인데 실행파일의 크기가 1기가 라고 하면 과연 메모리에 올라가있는 코드만 프로세스인가? 

틀린건 아니지만 너무 애매한 표현이다.



프로세스의 구체적인 이해


프로세스의 범위

메모리 구조 + 레지스터 Set

프로세스 별 독립적인 대상은 프로세스의 범주에 포함시킬 수 있다.




 ↑

 Code 영역

명령어

(Instruction Code)

   

 ↑

Data 영역

 전역변수

static 변수

   

Heap 영역



Stack 영역

 프로그래머 할당

   

 



   

 지역변수

전달인자 정보

   
       

프로세스가 생성되면 위와같은 메모리가 할당 된다. 

프로그램이 실행된다라는 것은 레지스터에 프로그램 실행정보로 가득차게되는데

이런 레지스터 set과 메모리 구조 모두 프로세스 범위에 포함된다.


'프로세스를 구성하는 범주는 프로세스 별로 독립적으로 할당받는 리소스다.'

라고 할 수 있다.



프로세스 스케줄러


프로세스 스케줄러 기능

둘 이상의 프로세스가 적절히 실행되도록 컨트롤


스케줄링 방법

스케줄링 알고리즘에 따라 다양함.



스케줄러 라는 장치는 Windows 에서 지원해주는 소프트웨어적인 장치이다.

프로세스 스케줄러는 말그데로 프로세스를 스케줄링 해주는 것이다.

CPU는 하나인데 프로세스는 여러게를 실행 시키려하면 CPU는 하나의 프로세스만 실행시킬수 있기때문에 스케줄러가 프로세스의 순서를 정해주고  순서를 정하는 정책을 결정하고 관리해주는 것이다.


§스케줄러는 소프트웨어이기 때문에 스케줄러가 동작하는 순간에도 프로세스들은 동작하지 못한다. 넓은 관전에서 보면 스케줄러도 프로세스에 포함시킬 수 있다.






프로세스의 상태




프로세스는 Ready , Running, Blocked 같은 상태정보를 지닌다.

Running 상태는 CPU에 의해서 실행중인 상태로 CPU가 하나라면 Running 상태의 프로세스는 하나일 수 밖에 없다.

Blocked는 실행중이 아닌 상태.

Ready 상태는  Running 하고 싶은 상태로 C프로세스가  Running 상태이고

A와 B가 Ready 상태일때 C가 Blocked 상태로 간다고 하면 A와 B중 누가 Running

상태로 가느냐를 정해주는 것이 바로 스케줄러가 하는것이다.   

즉 Ready 상태에 있다는 것은 스케줄러가 선택해주기를 기다리는 상태이다.

그림에서 볼 수있듯이 모든 프로세스는 실행시키면 Ready 상태가 된다.

연산중에는 CPU에 의존적인 연산이 있고 그렇지 않은 연산있다.

그중에 I/O연산은 유독 CPU에 의존적이지 않기때문에 사칙연산과같은 기본적인 연산과 병행해 질수있다. 

만약에 Running 상태에 있는 C라는 프로세스가 I/O 연산시작 했을때 I/O 연산이 끝날때 까지 C는 쉬고있고 대신 다른 프로세스가  Running 상태가 되어도 된다.

그럼 C는 Ready 상태가 되야될까? Blocked 상태가 되야할까?

 Ready 상태가 된다고 생각해보면 C는 다시  Running 상태가 될것이고 I/O연산이 끝나지 않았다면 다시  Ready 상태가 되는 반복을 하기때문에 Blocked 상태가 된다.

그리고 다시 I/O연산이 끝났다면 Ready 상태로 돌아가게 된다.

 Running 상태에서 바로 Ready 상태가 되는것은 다른 프로세스들고 실행을 시키여하기때문에 연산이 끝나지않았어도 Ready 상태로 내리는 경우이다.


1. 모든 프로세스는 실행시키면 Ready 상태가 된다.

2. Ready 상태이면 스케줄러가   Running 상태로 만들어준다.

3. Running 상태에서 다른 프로세스를 실행시키기위해 어쩔수 없이 양보하는 경우

    Ready 상태로 내려간다.

4. 일반적으로 I/O 연산을 하고있는 프로세스들은 다른프로세스들에게 양보하기위        

   해서 Blocked 상태로 간다.

5. I/O 연산이 끝나면 프로세스를 다시 진행시키기위해  Ready 상태가 된다.

6. 프로세스 종료.




컨텍스트 스위칭(context switching)




ALU가 연산을 하기위해서 레지스터에 의존적이다.

레지스터는 Running 상태의 프로세스 정보로 가득차있다.

이때 Ready상태의 프로세스를 실행 시키려 할때 프로세스를 실행시킬 수 있는 

첫 번째 조건은 지금 레지스터레 채워져있는 데이터를 다른 저장영역에 저장시키고

 Ready상태의 프로세스를 위한 데이터를 레지스터에 복원해야한다.

이런 작업을 컨텍스트 스위칭(context switching)이라 한다.













Direct 모드와 Indirect 모드


Direct 모드                              Indirect 모드          

  LOAD r1, 0x10 실행시                LOAD r1, [0x10] 실행시              

 r0

 

 r0

 
 r1

 ←10     0x10번지

 r1

 0x30                  ↓                 0x10번지

 r2   r2

   ↖                   ↓

 r3

  r3

       ↖   10                            0x30번지

 r4     ir

 

 r4     ir

 
 r5     sp  

 r5     sp

 
 r6     lr  

 r6     lr

 

 r7     pc

 

 r7     pc

 


Direct 모드는 말그데로 메모리에 있는 값을 레지스트로 가져와라 이고

Indirect 모드는  메모리에 있는 값을 다시 메모리로 참조에서 값을 가져오는것이다.  0x10[0x30] → 0x30[10] → r1

※  LOAD r1, [0x10] 여기서 []는 Indirect 모드 라는 표시로 아무거나 써도 상관은없다.



Direct 모드의 문제점


 

 <-  LOAD  ->

 <destination>

<-                    source                   ->

 0

 0

 1

1

0

 0

1

 1

 0

 0

 0

1

 1

 1

   

int a = 10;      //0x0010 번지할당

int b = 20;     //0x0100 번지 할당

int c = 0;       //0x0020 번지 할당

c = a + b;

                                                                                      

LOAD r1, 0x0010 (구성가능)

LOAD r2, 0x0100 (구성불가)


8비트로 표현가능한 수는 0~255이다.

LOAD 명령어를 살펴보면 source 부분은 8비트 밖에 되지않는다.

0x0010같은 경우는 8비트로 표현이 가능하지만 0x0100은 딱봐다 8비트로는 표현이가능하지않는다. 

그럼 명령어로 구성 할 수없는 메모리 주소는 어떻게 사용할 것이가??



문제해결


int a = 10;      //0x0010 번지할당

int b = 20;     //0x0100 번지 할당

int c = 0;       //0x0020 번지 할당

c = a + b ;

명령어로 표현해보면 메모리 주소값문제로 Direct 모드로 표현 할 수없다는 것을 알고있다.

그래서 Indirect 로 풀어보면 


LOAD r1 , 0x0010


MUL r0, 4, 4

MUL r2, 4, 4
MUL r3, r0, r2


STORE r3, 0x0030

LOAD  r2, [0x0030]


ADD r3, r1, r2

 일단 r1에 a값을 넣는것은 그냥 LOAD명령어를 넣으면 된다 

하지만 b값은 메모리주소 0x0100 에 할당되어있어서 명령어로 표현이 불가능하다 그래서 MUL 명령어를 이용해보자.

r0에 4 곱하기 4 연산값을 넣고 r2에도 반복하자.

그리고 r3에 r0와 r2의 값을 곱한값을 넣으면 16*16 즉 256 , 16진수로 0100 이다. r3에보면 0x100이 들어가 있는상태이다.

이제 r3에 있는 값을 0x0030 번지에 값을 넣고 Indirect로 0x0030을 불러와서 r2에 넣으면 r2에 원하던 값을 넣을 수 있게 된다 .


     레지스터                                 메모리

 r0         16

 

 r1         10

   10                                 0x0010(a)

 r2         16

   0                                  0x0020(c)

 r3     265(0x0100)

   
 r4   ir

  0x0100                               0x0030

 r5   sp

  20                                      0x0100

 r6   lr  
 r7   pc  


그림으로 만들어 보면 이런 모양일 것이다.




LOAD & STORE 명령어 디자인


LOAD & STORE 명령어의 필요성


명령어의 제한

사칙연산의 피 연산자는 숫자 또는 레지스터.

연산결과는 레지스터에 저장.

레지스터를 통해서 모든 연산을 진행 해야 한다.




사칙연산 명령어 구성

 <-예약->

 <-  연산자  ->

 <-  저장소  ->

<-    피연산자1    ->

<-     피연산자2   ->

                

                      ADD                  r2                        r1                            7


저장소 : 레지스터

피연사자1,2:레지스터 or 숫자


챕터①에서 명령어의 구성을 살펴보면  연산결과를 레지스터에만 저장 할 수있도록 제한이 되었다. CPU로 오가는 데이터는 모두 레지스터를 통하기 때문에 일단 레지스터에 저장을 해야한다. 

그럼 메모리를 목표로 할때 레지스터로 통해 나간다 치고 

명령어에 메모리주소를 넣으면 좋지 않을까? 

복잡한 명령어의 CISK에서는 가능하지만 RISK에서는 성능향상을 위해 어쩔수없다.

 


문제


 int a=10; //0x10 번지 할당

int b=20; //0x20 번지 할당

int c=0;  //0x3 0번지 할등


c=a+b;



그러면 !!

왼쪽과같이 메모리에 있는 연산자를 연산하려고한다. 하지만 연산자구성을 보면 피연산자는 레지스터 또는 숫자만 가능하도록 제한하였다.

그래서 메모리에 있는 값을 레지스터로 불러들이고 반대로 메모리에 연산결과를 저장하는 것을 위해

LOAD & STORE 명령어 가 필요한 것이다.








LOAD & STORE 명령어 디자인


LOAD 명령어


 <-예약->

 <-  LOAD  ->

 <destination>

<-                    source                   ->

  

 1

           

                                                                                          

destination : 데이터를 저장할 레지스터 정보        LOAD r1, 0x20

source : 데이터를 읽어올 메모리의 주소 정보



STORE 명령어


 <-예약->

 <-  STORE  ->

 <destination>

<-                    source                   ->

  

 1

1

           

 

source : 데이터를 읽어올 레지스터 정보             STORE r1, ox20

destination : 데이터를 저장할 메모리의 주소 정보


※명령어 2진코드 110, 111은 사칙연산에 겹치지않게 만든것이다.


문제해결

int a=10; //0x10 번지 할당

int b=20; //0x20 번지 할당

int c=0;  //0x3 0번지 할등

c=a+b;

LOAD r1 , 0x10

LOAD r2 , 0x20

ADD r3, r1, r2

STORE r3, 0x30

 r0

 메모리

 r1

←a    10                                           0x10

 r2

 

 r3        

↖b    20                                           0x20

 r4       ir

 

 r5      sp

 
 r6      lr   c    30                                           0x30
  r7     pc 

ㄴㅇㅁㄴㅇㅁㄴㅇㅁㄴㅇ


 c=a+b를 이러한 명령어 조합으로 만들어 볼수있다. 

메모리에 있는 값을 LOAD명령어로 불러들여온후 ADD 명령어로 연산을 하면 사칙연산에서의 피연사자 문제를 해결 할 수있다.




컴퓨터 구조의 접근방법

컴퓨터를 디자인 하자


프로그래머 관점

컴퓨터 구조를 잘 아느 프로그래머도 컴퓨터 디자인에 참여

컴퓨터 디자인은 레지스터와 명령어 디자인

CPU 자체에 대한 표준은 없기 때문에 CPU의 종류는 무궁무진하다.

예를 들어 GPU 도 그래픽처리에 제한된 CPU라 할 수 있다.

하드웨어 전문가 뿐만 아니라 프로그래머도 CPU디자인에 참여한다. 디자인을 할 때 로직,하드웨어,알고리즘,인터페이스 전문가 들이 참여하게되는데 그중에 프로그래머도 포함 될 수있다. 이 과정에서 프로그래머는 주로 레지스터와 명령어 디자인을 맞는다. 



레지스터 디자인의 핵심   

레지스터는 몇 비트로 구성할 것인가?

몇 개 정도로 레지스터를 구성할 것인가?

레지스터 각각을 무슨 용도로 사용할 것인가?

레지스터는 컴퓨터 시스템과 같은 크기로 구성하는것이 보편적이다.

n비트 시스템에서 명령어의 크기가 n비트이기때문에 레지스터에 한번에 데이터를 저장하기위해 레지스터를 같은 크기로 구성하는 것이다.
레지스터의 수는 많으면 많으 수로 좋다.

레지스터는 특별한 용도를 갖고 있는 저장장치이다.

램이나 하드디스크는 범용적으로 사용하지만 레지스터는 용도가있는 저장장치이기때문  용도를 정하고 그로인해 명령어가 단순해지고 속도도 증가하게 된다.




한번 레지스터를 디자인해 보자.


   r0

 






   ir : instruction register

   sp : stack point

   lr : link register

   pc : program 

   r1
   r2
   r3

   r4        ir

   r5        sp

   r6        lr

   r7        pc


16비트 크기의 레지스터 8개를  구성했다.

각가 r0 ~ r7 이름을 붙이고 r4 부터 r7까지는 용도를 지정하였다. 

r0 ~ r3 는 범용레지스터로 연산을 위해 필요한 레지스터라 생각하자.


밑에 계속....




명령어 구조 및 명령어 디자인


명령어의 기본 모델

16비트 명령어


사칙연산 명령어 구성

 <-예약->

 <-  연산자  ->

 <-  저장소  ->

<-    피연산자1     ->

<-     피연산자2    ->

                               

                      ADD                  r2                        r1                            7

저장소 : 레지스터

피연사자1,2:레지스터 or 숫자



연산의 의미 

심볼 

2진 코드 

 

레지스터 심볼 

2진 코드 

덧셈 

ADD 

001 

 

r0 

000 

뺄셈 

SUB

010

 

 r1

001

 곱셈

 MUL

011

 

 r2

010

 나눗셈

DIV

100

   r3

011

       

 r4 , ir

100

         r5 , sp

101

         r6 , lr

110

         r7 , pc

111



레지스터 디자인 쪽에서 16비트 레지스터를 디자인 했다. 시스템과 레지스터의 크기가 같아야 이상적이라 했는데 명령어의 크도 같아야 한다. 그렇기에 16비트 크기의 명령어를 디자인하였다.

명령어 하면 단순한 정보를 담고있을 거라 생각 할 수 있지만 명려어 안에는 

연산자, 저장소, 피연산자 같은 많은 정보를 담고 있다.

위에 디자인된 명령어를 보자. 각가 연산자에 3비트 저장소에 3비트 피연산자에 4비트를 할당 하였다. 

※명령어에 따라 피연산자가 1개가 존재 할 수도 있고 그럴때에는 구조가 달라진다.

저장소는 레지스터만 오도록 하였는데 연산결과는 일다 레지스터에 저장한다.

피연산자는 레지스터 이거나 숫자 가 올 수 있는데 계속....


피연산자에 레지스터나 숫자 모두 올수있는데 만약 0001일때 이것이 숫자인지 레지스터인지를 알 수 없다 그렇기에 맨앞 비트에 따라 레지스터나 숫자인가 하는 구분하는 약속을 하여야한다. 이런식을 제한을 하지않으면 명령어에 낭비가 오고 명령어가 복잡해진다.


명령어는 왜 단순하게 만들어야 하는가?

위와같이 단순한 구조를 가리켜 RISK 라 하고 이와 반대대는 구조로 CISK 가 있다.

CISK 구조는 명령어가 복잡하여 명령어를 다양하게 조합 원하는것을 대부분 반영할 수 있는 구조이다.

RISK 구조는  CISK의 복잡한 명령어를 단순화 시킨것인데 이로인해 제한사항이 많이 생긴다. 명령어가 단순해서 표현을 하는데 있어 CISK에 비해 더 많은 명령어가 필요하게 될 수 있다. 그럼에도 불구하고 오늘날 대부분의 CPU는 RISK 이다.

이는 속도를 향상시키는데 있어서 RISK가 고성능컴퓨터에 적합하다.

명령어를 처리하는 과정을 1장에서 보았듯이 Fetch,Decode,execution 3단계를 거치는데 각각의 단계를 처리하는데 1클럭이 걸린다고 가정한다면 5개의 명령어를 처리한다고 보면 단순계산으로 15클럭이 필요할 것이다. 하지만 각처리과정의 로직이 별도이기때문에 동시에 처리하여도 된다. 

F D E

   F D E

      F D E

이런식으로 첫번째 명령어를 Decode 할때 두번째 명령어를 Fetch 하는 식으로 동시에 진행하게 되고 명령 5개를 실행하는데 있어 7클럭이 필요하게 된다.        CISK 보다 적은 클럭으로 명령어를 처리 할 수 있기에 고성능 컴퓨터에서 RISK구조를 사용하는 것이다.











64비트 기반 프로그래밍

64비트 기반 프로그래밍

64비트 시스템을 고려한 프로그래밍으로 자료형에 대해서 고려해야 한다.


LLP64 vs LP64

32비트 시스템과의 호환성을 중시한 모델


운영체제 

모델 

char 

short 

int 

long 

포인터 

Windows 

LLP64  

1b yte 

2 byte 

4 byte 

4 byte 

8 byte 

UNIX 

LP64 

1 byte 

2 byte 

4 byte 

8 byte 

   8 byte    



Windows 에서는 LLP64 모델을, UNIX 계열에서는 LP64 모델을 채택하고있는데

32비트 시스템과의 호환성을 고려하여 기존과의 차이는 포인터에서만 볼 수있다.



64비트와 32비트 공존의 문제점

데이터 손실의 문제    


#include<stdio.h>

int main(void)

{

int arr[10]={0,};

int arrVal=(int)arr;

printf("pointer : %d \n ",arrVal);

return 0;

}


위 예제를 보면 arr이라는 배열의 주소값을 int형으로 강제형변환을 통하여 출력하려한다. 32비트 시스템이라면 포인터가 4byte이기때문에 문제가 발생하지 않을 것이다.

하지만 64비트 시스템이라면 arr 이 8byte 이고 int형은 4byte 이기때문에 문제가 발생 할수 있다. 

※64비트 시스템에서는 포인터를 기본자료형으로 변환하지말자!



Windows 스타일 자료형

Polymorphic 자료형

#if define(_WN64)

typedef __int64 LONG_PTR;

typedef unsigned __int64 ULONG_PTR;

typedef __int64 INT_PTR;    

typedef unsigned __int64 UNIT_PTR;


#else 

typedef long LONG_PTR;

typedef unsigned long ULONG_PTR;

typedef int INT_PTR;

typedef unsigned int UINT_PTR;


#endif


Polymorphic 자료형은   _WIN64 를 정의 했을때 다형적 자료형이 정의된다.

하나만 예로 들어  UNIT_PTR 은 64비트일때 unsigned __int64로  해석되고

32비트일때는 그냥  unsigned int로 해석된다.

PTR이 들어가서 포인터형으로 오해 할 수 있지 Polymorphic 자료형은 포인터 타입이 아니다.

PTR이 들어간 이유는 포인터 연산을 활용하기 위한 자료형일 뿐이다.

밑의 예제를 보자.




예제3-1]

UINT CalDistance(UINT a, UINT b)

{

return a-b;

}


int _tmain(void)

{

INT val1=10;

INT val2=20;

 _tprintf(_T("Position %d, %d \n"),

(UINT)&val1, (UINT)&val2);

_tprintf(_T("distance : %d \n"),

CalDistance((UINT)&val1,(UINT)&val2)

 

);

return 0;


}


예제를 보면 val1과 val2의 거리를 구하는 예제이다.

하지만 UINT 자료형은 4byte이다. 32비트시스템이면 몰라도 64비트시스템에서는 정상적으로 안될 수 있다.

이럴때 UINT 대신 UINT_PTR을 쓰면 32비트 일 때는 unsigned int 로

  64비트 일 때는  unsigned __int64로 

해석되어 문제가 발생하지 않고 메로리 낭비도 줄일 수 있다.

#if define(_WIN64)

typedef unsigned __int64 UNIT_PTR;

#else

typedef unsigned int UINT_PTR;

#endif



GetLastError 함수와 에러코드


함수호줄의 성공여부 확인의 기본

 GetLastError 함수 호출

windows시스템에서 오류가 발생 할 경우 전역 메모리공간에 오류의 이유가 저장이 된다. 그래서 여기서 가져와서 해석하면되는데 이 공간의 접근을 도와주는 함수가 GetLastError 함수이다.



Constant / value 

Description 

ERROR_ARITHMETIC_OVERFLOW
534 

 Arithmetic result exceeded 32 bits.

 ERROR_PIPE_CONNECTED

535

 There is a process on other end of the pipe.

 ERROR_PIPE_LISTENING
536

Waiting for a process to open the other end of the  pipe. 

ERROR_EA_ACCESS_DENIED

994 

 Access to the extended attribute was denied.

 ERROR_OPERATION_ABORTED

995

 The I/O operation has been aborted because of either a thread exit or an application request.

 ERROR_IO_INCOMPLETE
996

 Overlapped I/O event is not in a signaled state.

ERROR_IO_PENDING

997 

 Overlapped I/O operation is in progress.

 ERROR_NOACCESS

998

Invalid access to memory location. 

 ERROR_SWAPERROR

999

Error performing inpage operation. 


함수에서 반환되는 숫자로 오류를 알 수 있다.

MSDN에 가보면 더 많은 오류설명을 볼 수 있다.

실제 프로젝트에서 많이 쓰인다.




예제3-3]


int _tmain(void)

{

HANDLE hFile=

CreateFile(   //Windows system 함수.

_T("ABC.DAT"),GENERIC_READ,FILE_SHARE_READ,

NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,

NULL);

if(hFile==INVALID_HANDLE_VALUE)

{

_tprintf(_T("error code: %d\n"), GetLastError());


return 0;

}


return 0;                                           실행결과

}                                                     Error code: 2


CreateFile함수는 윈도우시스템함수이므로 크게 신경쓰지말자.

ABC.DAT 파일을 열려고 하는데 이런파일이 없다.  

그래서 오류가 발생했다는것을 알고 GetLastError 함수를 호출하면 에러 코드를 얻을 수 있다.


GetLastError 함수는 에러의 발생 이유를 확인하는 함수이지 에러가 발생했는지를 확인하는 함수는 아니다.

에러가 발생하면 그 원인이 전역공간에 저장된다고 하였는데 다른 함수가 호출되면 그 값이 변할 수 있다. 그렇기 때문에

에러가 발생이 됐는지 확인 이후 어느 함수보다 GetLastError 함수가 먼저 호출해야 정확한 에러이유를 정확히 알 수있다.



64비트와 32비트의 구분방법

한번에 송수신 가능한 데이터 크기

데이터가 메모리에서 I/O BUS를 통해 CPU로 전송되는 크기가 각 시스템 크기에따라

64비트, 32비트 로 나눌수 있다.


데이터 처리능력

위와 마찬가지로 CPU에서 한번에 처리 가능한 데이터 크기로 나눌 수 있다.

데이터 이동과 처리가 동일한 크기로 이루어져야 완벽한 시스템을 이룰 수 있다.


64비트 시스템에서 일반적으로 64비트 명령어를 사용하지만 32비트로 디자인하면 한번에 32비트 데이터 2개를 처리 할 수도 있다.


프로그래머 입장에서의 64비트 컴퓨터


32비트 → 64비트

프로그램으로 표현 할 수 있는 범위의 증가

표현할 수 잇는 메모리의 전체 크기


일반적으로 32비트 시스템에서 포인터의 크기가 32비트이고

64비트 시스템에서는 포인터의 크기가 64비트이다.

32비트 시스템이라면 메모리를 어드레싱 할 때 주소값을 2^32 만큼을 갖는데 32비트 시스템에서 4G이상의 메모리를 사용 못 하는 이유가 바로 이것이다. 

시스템이 64비트라면 표현할 수 있는 메모리의 크기가 2^64로 훨씬더 크게 사용 할 수 있다.


만약 64비트 시스템을 사용하지만 메모리가  2G라면 의미없지않은가?

이것은 가상메모리를 사용한다.




활용 가능 메모리의 확장


 
int _tmain(void)

{

TCHAR str[100];


_tscanf(_T("%s"),str);

_tprintf(_T("%s"),str);

return 0;

int _tmain(void)

{

TCHAR ch;

do

{

_tscanf(_T("%c"),&ch);

_tprintf(_T("%c"),ch);

}while(ch!="\n");


return 0;

}




















MBCS와 WBCS의 동시 지원

 WBCS 기반의 개발이 여러가지로 장점이 많지만 기존 개발 된 프로그램과의  호환성,

사용자 시스템의 유니코드 지원여부에 때문에 프로그램 개발에 이써 MBCS와 WBCS을 동시에 지원하는 프로그램을 개발 하여야 한다.


#include<windows.h>

Windosws 정의 자료형

typedef char        CHAR;

typedef wchar_t   WCHAR;

#define CONST const


typedef CHAR*              LPSTR;

typedef CONST CHAR*   LPCSTR;


typedef WCHAR*            LPWSTR;

typedef CONST WCHAR* LPCWSTR;

 

windows.h 안에는 이런 식으로 자료형이 정의되있는데 실제 개발에 있어서 꼭 이런식의 자료형을 사용하지는 않으므로 이정도만 알아두자. 


MBCS와 WBCS 동시 지원 매크로 

#ifdef UNICODE

  typedef  WCHAR     TCHAR;

  typedef  LPWSTR    LPTSTR;

  typedef  LPCWSTR  LPCTSTR;

#else 

 typedef   CHAR        TCHAR;

 typedef   LPSTR       LPTSTR;

 typedef   LPCSTR     LPCTSTR;

#endif

windows.h 보면 위와같은 매크로가 선언되어 있다.


만약  매크로 UNICODE 가 정의됬을 경우 


TCHAR arr[10];    →    WCHAR arr[10];     →    wchar_t arr[10];


매크로 UNICODE 가 정의되지 않았을 경우

 

TCHAR arr[10];    →    CHAR arr[10] ;       →    char arr[10];


이런식으로 처리가 된다.

이 매크로를 통하여 손쉽게 MBCS와 WBCS를 동시에 지원하는 프로그램을 개발 할 수있다.


#ifdef _UNICODE

     #define __T(x)   L ## x

#else

     #define __T(x)   x


#define _T(x)       __T(x)

#define _TEXT(x) __T(x)


WBCS 기반의 문자열을 선언 할 때 L"ABC" 이런식으로 정의하는것을 위한 매크로가 위의 매크로 이다. 

  #define __T(x)   L ## x  이 부분의 ## 처리자는 L과 x를 붙이라는 의미이다. 

매크로 _UNICODE 가 정의됬을 경우


_T("ABC");   __T("ABC");   L"ABC";


매크로 _UNICODE 가 정의되지 않았을 경우


_T("ABC");   __T("ABC");   "ABC";


매크로가 정의 되어있을떄를 보면 문자열이 WBCS로 치환되고

매크로가 정의 되지않았을 때 MBCS 기반으로 해석된다. 


#define _T(x)       __T(x)

#define _TEXT(x) __T(x)

이 부분은 _T(x), __T(x), _TEXT(x) 어떤걸 쓰던 상관은 없지만 주로 _T(x)를 쓴다.



예제 2-6]

#define UNICODE

#define _UNICODE


int wmain(void){


TCHAR str[]=_T("1234567");

int size=sizeof(str);

printf("string length : %d \n",size);                         실행결과

return 0;                                                               string length : 16

}

간단한 예제를 통하여 매크로를 정의했을때  TCHAR str[]=_T("1234567");부분이 유니코드 처리가 되었다는 사실을 결과값을 통하여 알 수있다.

(문자 7개에 null값포함 각각 2byte씩 이므로 문자열의 길이가 16이다.)

※유니코드에서 null값도 2byte이다. 나만 모르던 사실 일 수 있다.



MBCS와 WBCS 동시 지원 함수

#ifdef _UNICODE 
  #define _tmain      wmain 
  #define _tcslen     wcslen 
  #define _tcscat     wcscat 
  #define _tcscpy    wcscpy 
  #define _tcsncpy  wcsncpy
  #define _tcscmp   wcscmp 
  #define _tcsncmp  wcsncmp
  #define _tprintf      wprintf 
  #define _tscanf     wscanf 
  #define _fgetts      fgetws 
  #define _fputts      fputws
#else 
  #define _tmain      main 
  #define _tcslen     strlen 
  #define _tcscat     strcat 
  #define _tcscpy    strcpy 
  #define _tcsncpy  strncpy
  #define _tcscmp   strcmp 
  #define _tcsncmp  strncmp
  #define _tprintf      printf 
  #define _tscanf     scanf 
  #define _fgetts      fgets 
  #define _fputts      fputs
#endif 


많이 쓰는 MBCS와 WBCS 동시 지원하는 함수이다.




 

 

문자셋의 종류와 특징

 

SBCS(Single Byte Character Set)

문자를 1byte를 사용하여 표현 대표적인 문자셋으로 아스키코드(ASCII code)가 있다.

 

MBCS(Multi Byte Character Set)

MBCS에서 Multi는 2byte 이상을 써서 문자를 표현해서가아닌 문자종류에 따라서 다른크기의 표현방식을 사용한다 해서의 Multi 라 한다. 

 

WBCS(Wide Byte Charater Set)

SBCS보다 2배 큰 2byte를 써서 문자를 표현. 유니코드

 

우리가 흔히 사용하는 컴퓨터 시스템은 SBCS이 아니라 영문과 한글을 같이쓰는 경우가 대부분이기 때문에 MBCS를 사용한다.

MBCS의 사용은 효율적일 수 있으나  하드웨어의 발전으로 안정성을 위해 WBCS를 사용하는 것이 안정적이다.

 

예제2-1]

#include <stdio.h>
#include <string.h>

int main(void)
{
 char str[]="ABC한글";
 int size=sizeof(str);
 int len=strlen(str);

 printf("배열의 크기 : %d \n", size);              실행결과
 printf("문자열 길이 : %d \n", len);                배열의 크기:8

 return 0;                                                    문자열 길이:7
}

 

예제의 결과값을 보면 배열의 크기가 8 ,문자열의 길이가 7이라 나온다.

문자열을 보면 ABC는 영어로 각각 1byte 씩이고 한글은 각각 2byte 씩해서

7byte에 맨뒤 null 문자를 합하여 8이라는 정확한 값을 출력한다.

문자열의 길이가 7이라 나온다. 하지만 이 문자열의 길이는 5이다.

strlen()함수가 한글을 2문자로 인식을 하기때문에 이러한 문제가 발생 한다.

 

 

 예제2-2]

#include <stdio.h>

int main(void)
{
 char str[]="한글입니다";
 int i;

 for(i=0; i<5; i++)
  fputc(str[i], stdout);

 fputs("\n", stdout);

 for(i=0; i<10; i++)
  fputc(str[i], stdout);                            실행결과

 fputs("\n", stdout);                             한글
 return 0;                                             한글입니다
}

 

이번 예제를 통하여 앞선예제에서의 문제를 다시한번 볼 수 있다.

for(i=0; i<5; i++) 를 통하여 5번 루프를 시켰을때 한글 이라는 결과를 출력하고

10번 루프 시켰을때 한글입니다 이라는 출력이 나온다.

 앞선 예제 2-1,2-2 를 보면 한글이 들어갔을 때 시스템이 지원해 줄 경우 MBCS을 이용하는것보다 WBCS 기반으로 프로그래밍 하는것이 안정적이다.

 

WBCS 기반의 프로그래밍

WBCS를 위한 두가지

첫째: char를 대신 wchar_t

둘째: "ABC"를 대신하는 L"ABC"

 

WBCS 기반 문자열 선언 예

wchar_t str[]=L"ABC";

 

 

wchar_t 가 익숙하지 않을수 있지만 기본적으로 제공하는 표준자료형이다.

char형은 1byte로 문자를 표현하고 wchar_t 2byte로 문자를 표현한다.

또한 문자열을 선언 할 때 문자열 앞에를 붙여주어야 한다.

 

 

 예제2-3]

#include <stdio.h>
#include <string.h>

int main(void)
{
 wchar_t str[]=L"ABC";
 int size=sizeof(str);
 int len=strlen(str);   

 printf("배열의 크기 : %d \n", size);
 printf("문자열 길이 : %d \n", len);

 return 0;
}

 

예제 2-3을 컴파일 하면 오류가 발생 한다.

int len=strlen(str);를 보면  strlen함수는 strlen(char*) 식으로 char형 포인터타입을 전달하게 선언되어있는데 예제를 보면 wchat_t을 사용하므로 strlen함수를 wcslen함수로 대체하여야 한다.

 

문자열 조작함수

 

SBCS 함수

WBCS 기반의 문자열 조작 함수

strlen

size_t wcslen(const wchar_t* string);

strcpy

wchat_t* wcscpy(wchar_t* dest, const wchar_t* src );

strncpy

wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t cnt);

strcat

wchar_t* wcscat(wchar_t* dest,const wchar_t* src);

strncat

wchar_t* wcsncat(wchar_t* dest,const wchar_t* src, size_t cnt);

strcmp

int wcscmp(const wchar_t* s1,const wchar_t* s2);

strncmp

int wcsncmp(const wchar_t* , cinst wchar_t* s2, size_t cnt);

 

 

예제2-4]

#include <stdio.h>
#include <string.h>

int main(void)
{
 wchar_t str[]=L"ABC";
 int size=sizeof(str);
 int len=wcslen(str);

 printf("배열의 크기 : %d \n", size);              실행결과
 printf("문자열 길이 : %d \n", len);                배열의 크기:8

 return 0;                                                    문자열 길이:3
}

예제2-3를 위의 처럼 바꾸면 컴파일이 되고 실행결과도 올바르게 나온다.

하지만 printf()함수 부분을 보면 WBCS방식으로 처리하고있지않는다.

밑에 표를보고 WBCS 기반의 입출력을 해볼수 있다. 

 

 

문자열 입출력 함수

 

 

 SBCS 함수

WBCS 기반의 문자열 입출력 함수 

 printf

int wprintf(const wchar_t* format [, argument]...); 

scanf 

int wscanf(const wchar_t* format [,argument]...); 

 fgets

wchar_t* fgetws(wchar_t* string, int n, FILE* stream);

 fputs

int fputws(const wchar_t* string, FILE* stream); 

 

예제2-5]

#include <stdio.h>
#include <string.h>

int main(void)
{
 wchar_t str[]=L"ABC";
 int size=sizeof(str);
 int len=wcslen(str);

 wprintf(L"Array Size    : %d \n", size);
 wprintf(L"String Length : %d \n", len);

 return 0;
}

 

입출력 함수도 WBCS 기반의 함수로 바꾸었다. 하지만 매개변수 전달자의 유니코드화가 되지않았다. 

 

 

 

 

 

 

 

매개변수 전달이자들 모두 WCBS 기반으로 바꿔주기 위해서는

main함수 대신해서 wmain함수를 선언해 주면된다. 또한 전달하는 인자들도

wchar_t 타입으로 바꾸어 주어야 한다.

 

 

 

프로그램 실행과정



처리기에 의한 치완작업


#으로 시작하는 지시자들 ex. #include , #define


컴파일러에 의한 번역


명령어 사용에 어려움을 느끼고 프로그램언어를 제작.

이런 프로그래밍언어를 명령어로 해석하는데 이과정이 컴파일러에 의해 이루러짐.


어셈블러에 의한 바이너리 코드 생성


위에 그림에서 처럼 cpu 개발 과정에서 명령어를 디자인하는데 0011은 덧셈,0010은 뺄셈 식으로 바이너리코드 할당. 이 코드로 프로그램을 개발하기에는 너무힘듬 이에 명령어를 이용.

이런 명령어를 미용한 프로그램밍을 코드로 재해석하는것 어셈블러가 한다.


링커에 의한 연결과 결합


어셈블러를 통해 만들어진 바이너리와 라이브러리를 결합하여 실행파일을 만듬.

 

 

Stored Program Comdept ?

프로그램이 메모리에 저장되는 구조 

명령어가 메모리 저장상태에서 CUP로 가서 해석 후 연산 진행 개념.

 

 Fetch

CPU 내부로 bus interface를 통하여 명령어가 이동.

Decode

control unit 이 명령어 해석

Execution

ALU 가 중심이 되어 연산이 이루어짐.



※Stored Program Concept 기반의 컴퓨터 구조


CPU와 메모리 관점에서 볼때 크게 위 3개의 버스로 나눌 수 있다.

버스를 하면 데이터 버스만 생각하기 쉽지만 어떤 데이터를 가져가야 할 지를 알아야 하기때문에 어드레스 버스가 존재. 또한 CPU와 메모리의 데이터는 양방향이기 때문에 

일의 순서를 정하기위해서 컨트롤 버스가 필요함.



+ Recent posts