문제 - 연도와 월을 입력하여 달력이 출력되는 프로그램을 작성하시요
[조건]
1년 1월 1일은 월요일이다
4년마다 한 번씩 윤년(원래 2월은 28일까지, 윤년인 경우는 2월은 29일까지임)
단, 100년마다는 윤년이 아님
또한 400년마다는 다시 윤년임
(따라서 100년, 200년은 윤년이 아니지만,400년, 800년, 2000년 등은 윤년임)
[출력형태]
년도를 입력하시오: 2009
월을 입력하시오: 6
일 월 화 수 목 금 토
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
#include <stdio.h>
int CheckLeapYear(int year); //입력된 년도의 윤년 여부 확인
int StartDayofMonth(int year,int month, int isLeapYear); //입력된 월의 시작 요일 확인
void PrintCalendar(int startDay, int month);// 해당 월의 달력 출력
int day[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; //월별 일수
int main(void){
int year, month;
int isLeapYear; //윤년여부 판단
int startDay; // 원하는 달의 시작요일
printf("년도를 입력하시요.\n");
scanf("%d", &year);
printf("월을 입력하시요.\n");
scanf("%d", &month);
isLeapYear=CheckLeapYear(year);
startDay=StartDayofMonth(year, month, isLeapYear);
PrintCalendar(startDay, month);
return 0;
}
int CheckLeapYear(int year){
if(year%4==0 && (year % 100 !=0 || year % 400 ==0))
return 0; //윤년
else
return 1; //윤년아님
}
int StartDayofMonth(int year, int month, int isLeapYear){
int i;
int startDay; //원하는 달의 시작요일
int totalDateCount; // 전체일수 계산
totalDateCount = (year -1 )*365 + (year-1)/4 - (year-1)/100 +(year-1)/400;
//입력년도 이전년도까지의 전체일수 계산
// (year-1) / 4 : 4년마다 한번씩 윤년
// (year-1) / 100: 100년마다는 윤년이 아님
// (year-1) / 400: 400년마다는 윤년임
if(isLeapYear==0)
day[1] = 29; // 윤년인경우 2월을 29일로 설정
for(i=0; i<month-1; i++)
totalDateCount = totalDateCount + day[i]; //이전년도까지의 전체일수 + 원하는 달 이전까지 날짜수 합계
startDay = totalDateCount % 7 ;
//월 화 수 ..... 일 순으로 계산될때
// 원하는 달의 시작요일은 0 은 월요일...6 은 일요일
return startDay;
}
void PrintCalendar(int startDay, int month){
int i;
int count = 0;
printf("일\t월\t화\t수\t목\t금\t토\n");
for(i=0;i<startDay+1;i++){ // 월요일기준 시작일이므로 1 더해서 공백 생성(시작이 일요일 부터 이기 때문)
printf("\t");
count++;
}
for(i=1;i<=day[month-1];i++){ //1일부터 날짜 출력
if(count>=7){ // 7개씩 찍고 줄바꿈
printf("\n");
count = 0;
}
printf("%d\t",i);
count++;
}
printf("\n");
}
컴퓨터의 문자 표현체계에 대해서 얼마나 알고 계산가요? 혹 아스키 코드, 아스키 확장코드, 유니코드 이 정도로 알고 있는것은 아니신가요?? 부끄럽지만 저는 유니코드의 표현 종류가 한 가지 종류가 아닌것은 알고 있었지만 만 면밀하게 알고 있지는 않았고 오늘 필요에 의한 자료를 찾아보면서 많이 부족했었다는 것을 깨닫게 되었습니다. 유니코드에도 생각외로 많은 종류의 표시집합이 존재하더군요. 예를 들면 UTF-8, UTF-16, UTF-32, UCS2, UCS4 같은 것 말이죠.
그래서 libiconv 라이브러리를 사용하기 위한 자료를 찾다보니 이 것에 대해서 꽤나 중요하다는 생각이 문득 들더군요. 그래서 일단은 간소하게나마 정리하는게 좋지 않을까 해서 올려봅니다.
1. 조합형( JOHAB )
비운이라고 생각해야 할까요? 한글을 표시하는 여러가지 표현방법 중 하나 입니다. 아주 옛날 도스에서 텍스트 에디터 같은 프로그램을 사용해보셨다면 조합형 한글, 완성형 한글등의 한글 표현 방법이 있다는 것을 아실겁니다. 그 중 하나이죠. 그런데 이 표시방법은 최근엔 거의 쓰이지 않습니다. 왜냐하면 Microsoft가 조합형이 아닌 완성형 한글을 채택했기 때문이라고 하네요. 역시 글로업 기업답게 파워가 막강한가 봅니다.
어쨌든 표현 방식은 16비트를 사용하게 되며, 최상위 비트는 1, 그 후엔 5/5/5로 비트를 나누어 초성, 중성, 종성을 표시하게 됩니다. 말 그대로 조합형이니 만큼 만약 초성에 ㄱ, 중성에 ㅏ, 종성에 ㅇ이라는 코드를 넣게 되면 해당 글자는 "강"이라는 글자를 표시하는 비트가 되게 됩니다. 무척 편리한 방법입니다. 예전 하드웨어 성능이 별로 좋지 않을때 한글 오토마타를 처리하는 것에 있어 완성형 보다 CPU도 덜 쓰고, 복잡하지도 않아 조합형이 좋다고 얘기될 때가 많았었는데 말이죠.
위 방식은 일반적으로 자주 쓰였던 조합형의 종류이고, 조합형에도 여러가지 종류가 있었다고 합니다.
1. N바이트 조합형
이 형태의 출력 방법은 자음 모음을 각각 분리하여 보관된 문자 형태에 맞는 값을 가지고 저장하는 방법입니다. 예를 들면 다음과 같네요. "블로그" 라는 문자에 대한 표기방법은 "ㅂㅡㄹㄹㅗㄱㅡ" 가 됩니다. 초기 때 자주 사용되었다고 하네요. 최대 5바이트까지 사용될 수 있었기 때문에 비효율성 때문에 사라졌을것 같습니다.
2. 3바이트 조합형
이 방법은 위에 언급한 초,중,종의 방식을 하나의 바이트로 사용한 것입니다. 만약 종성이 존재하지않는 "나"와 같은 문자일 경우 채움 문자를 적어넣었다고 합니다. n바이트 조합형 보다 효율적이기는 하지만 그래도 비효율 적이죠?
3. 2바이트 조합형
흔히 조합형이라고 하면 일컫는 방식입니다. 최상위 비트가 1, 초,중,종성을 각각 5비트로 나누어 표시한 방식이죠. 문제점은 두 번째 바이트의 최상위 비트가 0일 수 있기에 문자열 검색에서 문제가 생긴다고 합니다. 뭐 데이터가 NULL값과 동일하게 되어 문제점이 생기는 것 같습니다.
2. 완성형( EUC-KR )
이 문자 집합은 KS X 1001, KS X 1003 인코딩을 사용하는 문자형 입니다. EUC(Extended Unix Code, 확장 유닉스 코드)라는 이름에 걸맞게 확장 코드 중 KR 즉 한글을 표현하는 방식입니다. 표현 방식은 일반 아스키 코드들은 KS X 1003형식을 배당하고, 그 이상의 한글 코드들은 KS X 1001형식을 배당하게 됩니다. 보통 많이 들으셨던 것이 바로 이 방식일 겁니다. 문자 코드의 영역은 대충 아래와 같이 된다고 하는군요.
- 0x21 ~ 0x2C: 특수 문자(문장 부호, 그림 문자 등), 한글 낱자, 괘선 조각, 외국 문자(히라가나, 가타카나, 그리스 문자, 키릴 문자 등)
- 0x30 ~ 0x48: 한글 글자 마디 영역. 자주 쓰이는 2350자만 가나다 순서대로 배열
- 0x49: 사용자 정의 영역 A
- 0x4A ~ 0x7D: 한자 영역. 4888자를 한글 독음 순서대로 배열했으며, 독음이 다르고 모습이 같은 한자는 중복
- 0x7E: 사용자 정의 영역 B
이 문자 표현 형식의 단점은 모든 한글을 표현할 수 없는 단점이 생깁니다. 위에 언급했다 시피 자주 쓰이는 2350자만 배열하였기 때문에 문제가 되는 것이죠.
| B0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A0 | 가 | 각 | 간 | 갇 | 갈 | 갉 | 갊 | 감 | 갑 | 값 | 갓 | 갔 | 강 | 갖 | 갗 | |
| B0 | 같 | 갚 | 갛 | 개 | 객 | 갠 | 갤 | 갬 | 갭 | 갯 | 갰 | 갱 | 갸 | 갹 | 갼 | 걀 |
| C0 | 걋 | 걍 | 걔 | 걘 | 걜 | 거 | 걱 | 건 | 걷 | 걸 | 걺 | 검 | 겁 | 것 | 겄 | 겅 |
| D0 | 겆 | 겉 | 겊 | 겋 | 게 | 겐 | 겔 | 겜 | 겝 | 겟 | 겠 | 겡 | 겨 | 격 | 겪 | 견 |
| E0 | 겯 | 결 | 겸 | 겹 | 겻 | 겼 | 경 | 곁 | 계 | 곈 | 곌 | 곕 | 곗 | 고 | 곡 | 곤 |
| F0 | 곧 | 골 | 곪 | 곬 | 곯 | 곰 | 곱 | 곳 | 공 | 곶 | 과 | 곽 | 관 | 괄 | 괆 |
위 표는 KS X 1003에 해당하는 한글 부분 표 입니다. 위키피디아에 존재하는 것인데, 저장하는 방식이 일괄적이더군요. 일단 FIRST BYTE에 해당하는 문자는 제일 왼쪽 상단에 존재하는 B0 이라는 문자입니다. 그리고 두 번째 BYTE는 해당 글자에 위치하는 행+열에 해당되는 값이죠. 만약 "걔" 라는 문자를 저장하여 Binary로 표기하게 된다면 B0 C2 라는 데이터로 걔의 표기가 가능하게 됩니다.
http://ko.wikipedia.org/wiki/KS_X_1001_%ED%95%9C%EA%B8%80_%EB%B6%80%EB%B6%84_%ED%91%9C
3. 확장 완성형( CP949 )
완성형에서 자주쓰이는 2350자만 표현 가능하게 된 점이 있기에 모든 한글을 표기하기 위하여 추가적으로 확장된 방식입니다. 윈도우 95부터 채택되어 사용이 되었다고 하네요. 이 형태는 완성형인 EUC-KR과 하위 호완성이 존재합니다. 그리하여 A1~FE까지 사용하는 범위의 EUC-KR 과 더불어 빈 공간에 표현하지 못하는 한글을 추가적으로 맵핑하여 한 형태죠. 일단 이 것의 문제는 한글에 대한 순서가 빈공간에 차례대로 끼워넣은 만큼 순서에 대한 정렬 문제가 발생하게 됩니다.
4. UCS2
흔히 많이 보는 Windows에서의 Unicode라고 생각하시면 됩니다. 이 형태는 각국의 문자표기에 따른 문제점을 해소하기 위해서 바이트의 문자 표기를 위해 등장했고, 일률적인 형태를 위해 영문 문자 마저 2바이트의 공간을 사용하게 됩니다. 결국 영문 같은 경우 공간을 낭비하게 된다고 생각할 수도 있겠군요. 하지만 전체가 2바이트이기 때문에 기타 UTF 방식의 표현 형태보다는 처리가 간단하다고 할 수 있습니다. Windows 같은 경우엔 내부적으로 UCS2 문자열을 사용하는 것으로 알고 있습니다. 그 이유는 속도 처리가 빨라서라고 하는군요^^.
그래서 일반적으로 Windows 에서 개발 프로그래밍을 할 때에 L"아아아"와 같은 형태의 문자을 사용하면 UCS2의 방식을 따르게 됩니다. 물론 인코딩은 별개로 생각되야 하구요^^.
5. UCS4
2바이트를 사용하는 UCS2에 비해 UCS4는 하나의 글자를 4바이트로 표현합니다. 그 만큼 문자의 공간 낭비가 심하다고 할 수 있죠. 하지만 UCS2에서는 65,536개 이상의 문자 표현이 불가능하기 때문에 나타난 방식이라고 생각이 되네요 일단 기본적으로 UCS2로 표현이 되는 문자들은 UCS로 처리 표시되고 그 영역을 벗어나는 부분만 UCS4로 처리를 하게 됩니다. 그리고 GCC 같은 경우 유니코드 문자열은 Windows개발툴과 다르게 UCS2가 아닌 UCS4로 사용되므로 주의가 필요합니다. 이에 대한 소스코드의 호환성을 잘못 썼다간 아이쿠 하는 사태가 발생할 수도 있다는 아주 무시무시한 사실이 @0@
6. UTF-8
최근 많이 들어보셨던 단어 인가요? 개발자가 아닌 일반 사용자 분들도 최근엔 자주 보셨던 단어 일 겁니다. 바로 "URL을 항상 UTF-8로 보내기" 라는 옵션 때문이죠. 이 것의 체크 여부에 따라서 한글로 되어있는 그림이 보였다 보이지 않았다 하는 점 때문에 조금 이러저러한 문제가 있을겁니다. 하지만 결과적으로 말하자면 일단 UTF-8 형식을 사용하는 것이 옳다고 얘기할 수 있습니다. 왜냐구요? 이 것은 UCS2에서 말했다 시피 영문 코드마저도 2바이트 공간을 항상 사용해야 하기 때문에 존재하는 낭비를 줄이기 위해서 나온 형태 중에 하나 이기 때문이죠. 다만 다른 점은 UCS2, UCS4는 형태 그대로의 문자집합인데 비하여 하나의 인코딩 방법이라고 할 수 있습니다.
| 000000-00007F | 0xxxxxxx |
ASCII와 동일한 범위 |
| 000080-0007FF | 110xxxxx 10xxxxxx |
첫 바이트는 110 또는 1110으로 시작하고, 나머지 바이트들은 10으로 시작함 |
| 000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx | |
| 010000-10FFFF | 11110zzz 10zzxxxx 10xxxxxx 10xxxxxx |
UTF-16 서로게이트 쌍 영역 (yyyy = zzzzz - 1). UTF-8로 표시된 비트 패턴은 실제 코드 포인트와 동일 |
가장 왼쪽은 유니코드 의 범위이고 이 범위에 따라서 해당 문자열에 관련된 Binary 값이 변환될 수 있습니다. 그리하여 문자열에 대한 영어 공간을 절약할 수 있는 것이죠.
여기서 일어날수 있는 문제점은 바로 Microsoft 문서 편집기와의 호환성 문제 입니다. 흔히 BOM 이라고 불리는 Byte Order Mark 라는 것이 UTF-8에서는 아스키와의 확장성을 띄기 때문에 실제로 사용되지 않는데 최고의 문서 편집기(?)인 Notepad를 보시면 텍스트파일을 읽을 때 UTF-8에 해당하는 BOM마크를 필수적으로 써주어야 합니다. 그로인해 문제점이 생길 수 있을것이라 생각됩니다.
7. UTF-16
이 인코딩은 UTF-8과는 약간 의미가 다릅니다. UTF-8 같은 경우 아스키와의 호환성, 공간절약을 목적으로 사용한 것이지만 UTF-16은 65,536개의 문자 이상의 표현할 수 없는 문자를 표현하기 위한 인코딩 방식입니다. 고로 UTF-16은 UCS2와 거의 동일하다고 보시면 됩니다. 그래서 Notepad에서도 저장방식이 UTF-8 방식과 Unicode Big Endian, Unicode Little Endian 이 있는 것 같군요. 표현하지 못하는 기본 방식에 대해서는 위키피디아를 참조 하시면 좋을 것 같습니다.
후.. 너무 많군요.. 단순히 몇개 밖에 안될 것이라 생각했던 것이 너무나 많습니다. 저도 자료를 찾아 정리해가면서 적은 것이라 부정확한 정보가 존재할 수도 있겠군요. 하지만 한 곳의 정보만 참조한 것이 아니라 여러군데를 찾으면서 한 것이라 꽤 정확하게 정리한 것 같습니다. UTF-32 와 같은 추가적인 인코딩방법도 존재하는데, 이 런 인코딩을 필요할 때 자세히 알아서 필요할 때 사용하면 될것 같습니다.
FreeType을 사용하게 되면서 결과적으로 아주 많은 것을 공부하게 된것 같습니다. 문자열 셋에 대한 여러가지 인덱스 들과 변환 방법을 통한 여러가지 종류의 텍스트 지원을 생각하다 보니 이런 저런 문제점이 발견되고 그 것을 해결하기 위해 자료를 찾다보니 여기 까지 오게 된것 같군요. 나름대로 쓰기 편하게 FontLoader같은 클래스를 만들고 있으니 잘 된다면 공개를 해보도록 하겠습니다.
출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/system_programing/Book_LSP/ch05_Process
1 프로세스에 대해서
Hard Disk | | Memory
+-------------+ | |
| Program | | | ...
|-------------+ | COPY | +------------+
| Instruction |--|------>| | Process |
| Data | | | | Exec Image |
+-------------+ | | +------------+
| |
이렇게 프로그램을 직접실행시키지 않고, 메모리로 이미지를 카피해서 실행시키는 데에는 다음과 같은 이유가 있다.- 서로 완전히 독립적인 프로그램의 실행 가능
- 여러개의 이미지를 만들 수 있으므로, 멀티프로세스/멀티쓰레딩 지원
2 프로세스의 상태

- running 상태 : 실행가능한 상태를 말한다.
- waiting 상태 : 어떤 조건을 기다리는 상태.
- stopped 상태 : 실행이 중단된 상태.
- zombie 상태 : 실행이 끝나고, 메모리 상에서 프로세스의 이미지가 제거 되었으나 운영체제의 커널은 여전히 프로세스의 정보를 가지고 있는 상태. zombie에 대해서는 뒤에서 자세히 다루도록 하겠다.
3 프로세스의 모드
- 유저모드 : 사용자 연산을 위한 모드로 사칙연산과 같은 연산작업으로 사용자 권한으로 각정 명령이 실행된다.
- 커널모드 : 주로 컴퓨터의 자원인 메모리, 하드디스크등의 장치에 접근하기 위한 모드로, 커널권한으로 실행된다.

4 프로세스의 실행
#include <unistd.h> int execl(const char *path, const char *arg, ...);
- path : 실행되는 프로그램의 완전한 경로다.
- arg : 이것은 프로그램이 실행될때, 넘겨질 실행인자들로, 여러개가 정의될 수 있다. 더이상 넘겨질 실행인자가 없다는 것을 분명하 하기 위해서, 마지막에 NULL을 입력해줘야 한다. 간단히 ls(1)를 실행시키는 프로그램을 만들어 보자.
#include <unistd.h> int main(int argc, char **argv) { execl("/bin/ls", "ls", "-al", NULL); }
1 2 3 4 5 6 7 8 9 10 |
#include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { printf("Start\n"); execl("/bin/ls", "ls", "-al", NULL); printf("End\n"); // 실행되지 않는다. } |
$ ./execTest Start drwxr-xr-x 2 yundream yundream 4096 2008-02-29 00:08 . drwxr-xr-x 60 yundream yundream 4096 2008-02-29 00:08 .. -rwxr-xr-x 1 yundream yundream 6585 2008-02-29 00:08 execTest -rw-r----- 2 yundream yundream 81 2007-12-17 23:59 hello.txt -rw-r--r-- 1 yundream yundream 12 2007-11-26 23:58 test.txt -rw-r--r-- 1 yundream yundream 489 2007-11-26 23:53 write.c $우선 6번째 코드인 printf가 실행된건 분명히 확인할 수 있을 것이다. 그다음 7번째 줄인 execl이 호출되어서 /bin/ls -al 이 실행되었다. 그런데, 8번째 줄은 실행되지 않았다 ? 앞에서 말했다 시피, execl이 호출되면서 프로세스의 이미지 자체가 /bin/ls 로 덮어써져 버렸기 때문에, 8번째 코드가 아예 실행이 되지 않기 때문이다.
5 멀티.다중 - 프로세스
+---------+ +--------------+
| Process |----+---| Copy Process |
+---------+ | +--------------+
| +--------------+
+---| Copy Process |
| +--------------+
|
+--- ...
프로세스를 복사하는게 포크와 비슷하다고 해서 fork라는 이름을 붙이게 되었다.5.1 fork를 이용한 자식프로세스 생성
#include <unistd.h> pid_t fork(void);코드내에서 fork(2)함수를 호출하면, 자식프로세스가 생성이 된다. 이 과정은 자식이 부모의 유전학적 정보를 상속받는 것과 비슷한데, 실제 자식프로세스는 부모로 부터 많은 정보들을 그대로 상속받는다. 예를들자면, 부모프로세스의 정보들, 열려있는 파일, signal정보, 메모리에 있는 많은 정보들이다. fork함수가 성공적으로 수행되어서 자식 프로세스가 생성되면, 부모프로세스에게는 새로 생성된 자식프로세스의 PID가 리턴이 되고, 자식프로세스에게는 0이 리턴된다.
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main() { int pid; int i; i = 1000; pid = fork(); if (pid == -1) { perror("fork error "); exit(0); } // 자식프로세스가 실행시키는 코드 else if (pid == 0) { printf("자식 : 내 PID는 %d\n", getpid()); while(1) { printf("-->%d\n", i); i++; sleep(1); } } // 부모프로세스가 실행시키는 코드 else { printf("부모 : 내가 낳은 자식의 PID는 %d\n", pid); while(1) { printf("==>%d\n", i); i += 4; sleep(1); } } }컴파일 한 후 실행시켜 보기 바란다. 부모프로세스와 자식프로세스가 동시에 주어진 코드를 실행시키는 것을 확인할 수 있을 것이다. ps 를 이용하면 이들 프로세스와의 관계를 명확하게 확인할 수 있다.
$ ps -ef | grep forktest UID PID PPID C STIME TTY TIME CMD yundream 12119 8557 0 17:33 pts/0 00:00:00 ./forktest yundream 12120 12119 0 17:33 pts/0 00:00:00 ./forktest우리는 PID 12120을 가지는 자식프로세스가 생성되었음을 확인할 수 있다. PID 12120인 프로세스가 자식프로세스인 것은 PPID값을 이용해서 확인가능 하다. PPID 는 parent Process ID의 줄임말인데, PID, PPID 등에 대한 것은 이 문서의 후반부에 자세히다루도록 할 것이다.
5.2 fork와 exec를 이용한 새로운 프로세스의 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdio.h> #include <sys/wait.h> #include <sys/types.h> #define chop(str) str[strlen(str)-1] = 0x00; int main(int argc, char **argv) { char buf[256]; printf("My Shell\n"); int pid; while(1) { // 사용자 입력을 기다린다. printf("# "); fgets(buf, 255, stdin); chop(buf); // 입력이 quit 라면, 프로그램을 종료한다. if (strncmp(buf, "quit", 4) == 0) { exit(0); } // 입력한 명령이 실행가능한 프로그램이라면 // fork 한후 execl을 이용해서 실행한다. if (access(buf, X_OK) == 0) { pid = fork(); if (pid < 0) { fprintf(stderr, "Fork Error"); } if (pid == 0) { if(execl(buf, buf, NULL) == -1) fprintf(stderr, "Command Exec Error\n\n"); exit(0); } if (pid > 0) { // 부모 프로세스는 자식프로세스가 종료되길 기다린다. int status; waitpid(pid, &status, WUNTRACED); } } else // 만약 실행가능한 프로그램이 아니라면, 에러메시지를 출력 { fprintf(stderr, "Command Not Found\n\n"); } } } |
MY Shell $ myshell # /usr/bin/w 01:15:32 up 2:58, 4 users, load average: 0.47, 0.50, 0.62 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT yundream :0 - 00:05 ?xdm? 14:20m 0.05s /bin/bash /usr/ yundream pts/1 :0 00:06 9.00s 1.47s 1.24s w3m -F http://w yundream pts/3 :0 00:54 0.00s 0.22s 0.00s ./myshell yundream pts/4 :0.0 00:53 22:13m 0.40s 0.27s BitchX irc.nuri # ll Command Not Found # quit $
6 프로세스 관계
6.1 부모프로세스와 자식프로세스 init 프로세스
$ pstree
init─┬─NetworkManager───{NetworkManager}
├─NetworkManagerD
├─acpid
├─amarok───11*[{amarok}]
├─atd
├─avahi-daemon───avahi-daemon
├─bonobo-activati───{bonobo-activati}
├─console-kit-dae───61*[{console-kit-dae}]
├─cron
├─cupsd
├─2*[dbus-daemon]
├─dbus-launch
....
6.2 프로세스의 identify와 관계에서의 위치
- name : 프로세스의 이름이다.
- PID : Process ID로 운영체제가 각각의 프로세스에 부여하는 유일한일련 번호다. 프로세스의 이름만으로도 identify를 확보할 수 있을거라고 생각할 수 있지만 이름이 같은 프로세스가 생성될 수 있으므로, name만 가지고는 identify를 확보할 수 없다. 때문에 운영체제에서 일련번호를 부여하게 된다. 이 번호는 중복되지 않는 유일한 번호다. 일종의 주민등록번호 정도로 보면 될 거 같다.
- PPID : 부모프로세스의 ID로 어떤 프로세스로 부터 생성이 되었는지를 알려준다.
- PGID : 프로세스는 여러개의 자식프로세스를 만들어낼 수 있다. 그렇다면, 이들 프로세스는 {부모-->{자식,자식,자식}}과 같이 하나의 가계를 만들 수 있을 것이다. 운영체제에서는 이것을 가계라고 하는 대신 group이라고 한다. PGID는 프로세스가 어느 그룹에 포함되어 있는지에 대한 정보를 알려준다. PGID는 일련번호로 되어 있으며, 보통 부모프로세스의 PID즉 PPID가 PGID가 된다. 즉 프로세스그룹은 부모프로세스의 PID를 공통분모로 해서 하나의 그룹을 만들게 된다.
$ ps -efjc | grep forktest UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD yundream 12198 8557 12198 8557 TS 24 17:40 pts/0 00:00:00 ./forktest yundream 12199 12198 12198 8557 TS 21 17:40 pts/0 00:00:00 ./forktest프로세스의 상세정보들이 출력됨을 알 수 있다. PID가 12199인 프로세스가 12198로 부터 생성된 자식프로세스임을 확인할 수 있다. 또한 12198 프로세스는 PID 8557 로 부터 생성된 자식 프로세스임을 미루어 짐작할 수 있다. 그렇다면 PID 8557인 프로세스가 어떤 프로세스인지 확인해 보도록 하자.
$ ps -efjc | grep 8557 UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD yundream 8557 8550 8557 8557 TS 24 13:37 pts/0 00:00:00 bash그렇다. bash 프로그램임을 알 수 있다. bash는 우리가 forktest 프로그램을 실행시킨 쉘프로그램으로, bash도 fork()를 이용해서 forktest 를 실행시켰을 것임으로 forktest의 부모프로세스가 된다. 이들의 관계는 다음과 같은 Tree 형태로 표현할 수 있을 것이다.
fork&exec fork bash ---+----------- forktest ---+-------- forktestbash의 부모프로세스는 PID 8550을 가지는 프로세스일 것이고, 거슬로 올라가면 결국 init 프로세스를 만나게 될 것이다.
6.3 고아 프로세스
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main() { int pid; int i; i = 1000; pid = fork(); if (pid == -1) { perror("fork error "); exit(0); } // 자식프로세스가 실행시키는 코드 else if (pid == 0) { printf("자식 : 내 PID는 %d\n", getpid()); while(1) { printf("-->%d\n", i); i++; sleep(1); } } // 부모프로세스가 실행시키는 코드 else { printf("부모 : 내가 낳은 자식의 PID는 %d\n", pid); sleep(1); printf("T.T 나죽네\n"); exit(0); } }이제 실행시켜 보도록 하자.
$ ./forktest 부모 : 내가 낳은 자식의 PID는 8207 자식 : 내 PID는 8207 -->1000 T.T 나죽네 -->1001 yundream@yundream-desktop:~$ -->1002 -->1003 yundream@yundream-desktop:~$ -->1004 -->1005 -->1006 -->1007 -->1008쉘과는 따로 자식프로세스가 계속 실행되는걸 알 수 있을 것이다. 이제 Ctrl+C 를 눌러보자. Ctrl+C를 누르면 일반적으로 프로세스는 종료가 된다는 것을 경험적으로 알고 있을 것이다. - 정확히 말하자면 SIGINT라는 시그널이 전달되고, 이에 대한 반응으로 프로세스가 죽는다. 시그널은 나중에 다룰 것이다 -. 그러나 Ctrl+C를 아무리 눌러도 자식프로세스가 죽지 않는걸 알 수 있을 것이다. 왜냐하면, bash 의 자식의 자식 프로세스, 즉 같은 그룹에 속하지 않은 전혀 다른 그룹의 프로세스가 되었기 때문이다. ps 결과로 확인해 보도록 하자.
#ps -efjc | grep forktest UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD yundream 8207 1 8206 8093 TS 24 00:16 pts/5 00:00:00 ./forktestPPID가 1 즉 init의 자식프로세스가 되었음을 확인할 수 있다. 집도 절도 없는 고아프로세스라는 얘기가 되겠다.
6.4 Daemon 프로세스
- 일단 고아 프로세스가 되어야 한다.
데몬 프로세스는 완전히 독립된 프로세스다. 그러므로 고아 프로세스가 되어야 한다. 예컨데, 가족으로 부터 독립해서 사회로 나가야 된다는 얘기가 되겠다.
- 표준입력, 표준출력, 표준에러을 닫는다.
표준입력과 표준출력, 표준에러는 사용자와 프로세스가 상호작용 하기 위한 장치로, 표준입력은 키보드, 표준출력은 모니터로 대응된다. 데몬 프로세스는 뒤에서 독립적으로 돌아가는 프로세스 이므로 사용자와의 상호작용을 해서는 안된다. 그러므로 표준입력과 표준출력을 표준에러를 닫아줄 필요가 있다. 뒤에서 혼자 돌아야 하는 프로그램인데, 모니터에 (forktest2.c 와 같이) 잡다한 메시지를 출력해서는 안될 것이기 때문이다. 이런 데몬프로세스와의 상호작용은 IPC를 통해서 이루어진다. IPC는 Inter Process Communication 의 줄임말로 프로세스간 내부통신을 위해서 사용되는 설비이다. 회사내부에서 부서원간 통화를 위해 사용되는 전화라고 볼 수 있을 것이다. 이에 대한 내용은 별도의 장을 할애해서 다루도록 할것이다.
- 터미널을 가지지 않는다.
terminal(터미널)이란 사용자가 컴퓨터에 접속된 상태를 말한다. 이 터미널에 키보드와 모니터와 같은 장치가 연결되어 있고, 이것을 이용해서 사용자의 프로세스가 컴퓨터와 연결이 된다. 데몬 프로세스는 사용자 환경과 독립되어야 하므로 터미널을 끊어줘야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main() { int pid; int i; i = 1000; pid = fork(); if (pid == -1) { perror("fork error "); exit(0); } // 자식프로세스가 실행시키는 코드 else if (pid == 0) { printf("자식 : 내 PID는 %d\n", getpid()); close(0); close(1); close(2); setsid(); while(1) { printf("-->%d\n", i); i++; sleep(1); } } // 부모프로세스가 실행시키는 코드 else { printf("부모 : 내가 낳은 자식의 PID는 %d\n", pid); sleep(1); printf("T.T 나죽네\n"); exit(0); } } |
- 39 에서 부모프로세스를 종료한다.
- 22,23,24 에서 표준입력,표준출력,표준에러를 닫았다.
- setsid()를 이용해서, 사용자환경에서 독립된 자신의 환경을 만든다. 기존의 환경이 리셋되면서 터미널이 사라진다. 또한 새로운 터미널을 지정하지 않았기 때문에, 이 프로세스는 결과적으로 터미널을 가지지 않게 된다.
$ ps -efjc | grep daemon UID PID PPID PGID SID CLS PRI STIME TTY TIME CMD yundream 8252 1 8252 8252 TS 24 00:43 ? 00:00:00 ./daemonPPID가 1 이고, 새로운 Session ID인 8252를 가졌으며 (이 프로세스가 세션의 주인이므로, PID가 SID가 된다) 터미널(TTY)가 없음을 확인할 수 있다. 28번 줄의 printf 결과도 화면에 출력되지 않는 것을 확인할 수 있다. 완전한 데몬 프로세스가 만들어진 것이다.

Prev

Rss Feed