설탕 3kg, 5kg 봉지가 있는데 특정 kg수를 봉지를 최대한 적게 사용하여 만드려고 할때 가장 적게 쓰는 봉지의 수를 구하는 문제입니다.
18kg를 3kg봉지와 5kg봉지를 사용하여 채우려면 3kg짜리 6개를 써서 18kg를 만들 수도 있습니다.
하지만 5kg 짜리 3개와 3kg짜리 1개를 사용하여 만들면 총 4개의 설탕봉지를 사용합니다. 그러니까 4가 답이되는 것입니다.
3kg와 5kg만 사용하므로 만들 수 없는 수도 존재합니다. 그럴때는 -1을 출력하는 문제입니다.
입력은 채우는 설탕의 kg수 N이 주어지고 N의 범위는 3이상 5000이하(3<= N <=5000)입니다.
몇가지 입력에 대한 출력을 먼저 보고 문제를 풀어보시고 풀이를 봐주세요.
INPUT
OUTPUT
19
5
20
4
91
19
4999
1001
7
-1
풀이1 - DP
두가지 풀이가 있습니다. 전형적인 DP방식의 풀이와 그리디한 방식으로 푸는 방법이지요. 첫번째 풀이는 DP를 사용한 풀이입니다.
만약 현재 i kg를 달성하려면 이 전에 계산한 (i-3)kg와 (i-5)kg 중 작은 것에 1을 더하면 현재 i kg을 채울 수 있는 최소의 봉지수가 나오겠네요.
dp[i] = min(dp[i-3] + 1, dp[i-5] +1)
그런데 만들 수 없는 수는 0이겠죠? 예를 들면 4는 절대로 3과 5를 조합하여 만들 수가 없는데요. 그러면 항상 0이 최소의 수가 되니까 조건을 달아야합니다.
만약 dp[i-3]에 0보다 큰 값이 있을때, dp[i-5]일때 0보다 큰 값이 있을때 만 계산하게 만드는 것입니다.
코드는 아래와 같습니다.
#include <iostream>
using namespace std;
int dp[5001]; //global 변수이기때문에 0으로 초기화된 배열
int main() {
int n;
cin >> n;
//3kg와 5kg를 만드는 가장 최소의 봉지수는 1
//따라서 dp[3]과 dp[5]는 무조건 1
dp[3] = dp[5] = 1;
//3과 5 다음인 6부터 for loop 순회
for (int i = 6; i <= n; i++) {
if (dp[i - 3]) dp[i] = dp[i - 3] + 1;
//이미 dp[i-3]에 값이 존재할때 dp[i]가 업데이트 됐었을 수 있다.
//만약 dp[i]에 값이 없다면 dp[i] = dp[i-5] +1 로 업데이트
if (dp[i - 5]) dp[i] = dp[i] ? min(dp[i] , dp[i - 5] + 1) : dp[i - 5] + 1;
}
cout << (dp[n] == 0 ? -1 : dp[n]) << endl;
return 0;
}
먼저 dp라는 배열을 0으로 셋팅해놓고 (전역변수이기 때문에 저절로 0으로 초기화됩니다.) dp[3] = dp[5]를 1로 만든 후에 앞서 설명한 것을 for문 안에 구현하면 됩니다.
풀이2 - Greedy
설탕이 25kg가 있다면 5kg로만 계속 사용하여 채우면 됩니다. 그러면 5개 봉지만 사용하면 되겠네요. 그렇다면 우리가 kg을 사용할 일이 굳이 있을까요? 우리가 5kg으로 모두 채울 수 있다는 것을 미리 알면 오히려 3kg를 사용하면 불리하다는 것을 알 수 있습니다. 그렇다면 5kg으로 나눠진다는 것만 알면 되지 않을까요? 이때 mod연산 %연산이 사용됩니다. %5 연산을 하여 0이 되지 않는다면 3kg를 봉지를 사용해야겠죠.
그리고 난 후 다시 5kg로 나눠 떨어지는지 확인합니다. 만약 나눠떨어지면 나눈 몫만큼의 5kg봉지가 사용되고 프로그램을 바로 종료시키면 되는 아이디어입니다.
전체 C++ 소스코드는 아래와 같습니다.
#include <iostream>
using namespace std;
int N;
int main() {
cin >> N;
int ans = 0;
while (N>=0) {
if (N % 5 == 0) { //가장 큰 수로 나누는게 가장 작은수랑 섞어서 나누는 것보다 유리
ans += (N / 5); //나눈 몫을 더한 것이 정답
cout << ans << endl;
return 0;
}
N -= 3; //3kg을 빼고
ans++; //가방 하나 늘어남
}
cout << -1 << endl;
}
두가지 방식의 풀이를 알아보았는데 저도 얼핏 dp만 생각하고 있었는데 다른 사람의 해답을 보니 이러한 방법이 있다는 것을 알았습니다. 역시 저빼고 다른 사람들은 똑똑한것 같습니다.
겉으로 봐서는 무인도 같지만 사람이 살고 있는 섬입니다. 사람이 살고 있기는 한데, 절대 친절하지 않은 사람들이 살고 있어요. 그러니까 아무것도 모르고 들어갔다가는 이미 거기 살고 있는 형님들한테 화살맞고 죽을 수 있죠.
이곳에 살고 있는 “센티넬 족” 이라 불리는 원주민들이 살고 있습니다.
셀티넬 족은 6만 년이 넘도록 외부와 철저히 단절되어 있습니다. 그리고 외부인을 보면 먼저 죽이려고 듭니다. 그러니 뭣 모르고 섬에 들어갔다가는 ㅈ될수 있다는 겁니다. 그리고 앞으로 해외여행 리스트에서 북센티넬 섬은 지우도록 하세요.
적대적인 이유
센티넬 족은 왜 이렇게 우리에게 적대적일까요? 처음부터 그랬던 걸까요?
원래 먼저 선빵쳤던 건 외부인이었습니다. 원래 맞은 사람은 상처로 남고 때린 사람은 기억 못하잖아요? 저도 저 때렸던 사람 다 기억하거든요
예로부터 외부로부터 약탈을 당해왔습니다. 그리고인도가 영국의 식민지배를 당할 때 영국군의 무력 시위도 있었습니다. 특히 결정적인 것은 외부 한 탐험대가 노인 한쌍과 아이 4명, 총 6명을 납치하였습니다. 노인 2명은 면역력이 떨어져 사망했고 아이 4명은 돌려보냈다고 하네요.
그런 사건들이 있는데 외부인을 환영할 리가 없죠. 저같아도 제가 안죽으려면 활들고 먼저 쏩니다.
공격 사례
1974년 센티넬족을 다룬 다큐멘터리도 네셔널 지오그래픽에서 방영했었는데 센티넬 족들은 환영할리 없죠. 괜히 들어갔다가 부상을 입게 됩니다. 그래도 부상만으로 끝나서 다행인 정도인거에요.
괜히 불법으로 고기잡다가 술 먹고 배가 좌초되어서 죽은 사람들도 있으니까요. 실제 좌초된 배는 구글어스에 찍혀있습니다.
구글어스에 North Sentinel Island를 검색해서 그 배를 찾아보세요. 저는 찾았습니다.
접근 포기
2004년 남아시아 대지진이 발생했을 때 이 섬도 역시 큰 피해를 입었을 것이라는 추측으로 헬기를 보냈는데요. 물론 구호물자도 실어서 헬기를 보냈는데요. 묻지도 따지지도 않고 한결같이 그들은 화살로 대응하였고삔또 상한 정부는 그냥 알아서 복구하라고 냅뒀습니다.
마음의 상처가 컸던 탓일까요? 2005년 센티넬족과는 손절할 것이라는 발표를 하고 주위에 들어갈 수 없게 만들어버립니다.
사실 현대식 무기로 충분히 제압은 가능하지만 국제적으로 용납될 수 없고 인도적이지 않습니다. 그래도 나라가 인도인데
그밖에...
미 선교사 살해사건도 유명한 사건인데요. 괜히 종교 전파하러 갔다가 싸늘한 주검이 됩니다. 그들이 항상 외부인에게 공격적이지 만은 않았습니다. 인도의 조사단이 처음으로 그들과 교류하게 됩니다.
deadlock... 이름도 뭣같이 무섭네요. 교착상태라고 하는 것은 다중 프로그래밍 시스템에서 프로세스 혹은 스레드가 결코 일어나지 않을 일을 무한정으로 기다리는 상태를 말합니다. 시스템에서 교착상태는 프로세스가 요구하는 자원이 엉켜서 더 이상 작업을 더 실행할 수 없는 상태를 의미합니다.
시스템이라고 생각하면 조금 어려울 수 있는데 인간 세계에서도 똑같아요. 예를 들어볼까요?
평화로운 중고 사이트에서 A가 사고 싶은 노트북이 생겨서 B에게 연락을 합니다. 가뜩이나 요즘 중고사기가 판을 치는 시기에 B에게 먼저 노트북을 택배로 보내면 150만원을 입금해주겠다고 합니다. B 역시 A를 의심하여 먼저 입금하면 노트북을 보내겠다고 합니다. A는 B의 노트북을, B는 A의 돈을 요구하는 상황이고 서로 주지않습니다. 이렇게 서로 자원(돈, 노트북)을 점유한 상태에서 막혀 버려 서로 해야할 일(거래)을 하지 못하는 것입니다. 아무 것도 아닌 일 때문에 해야할 일을 못하는 것이지요. 어이가없네
교착상태의 발생 조건
아래 설명에서는 프로세스만을 이야기하는데, 스레드도 같습니다. 편의상 프로세스만 예를 들어 이야기하도록 하겠습니다.
1. 상호배제 (Mutual Exclusion)
한 번에 한 프로세스만 자원을 사용할 수 있어야합니다. 사용중인 자원을 다른 프로세스가 사용하려면 요청한 자원이 다 사용되고 난 후 해제될때까지 기다려야합니다.
2. 점유와 대기 (Hold And Wait)
프로세스는 자원을 점유한 동시에 대기해야합니다. 위의 예에서 볼 수 있듯이 A는 돈을 소유하고 있고 B가 노트북을 주기까지 대기하고 있습니다.
3. 비선점 (Non Preemptive)
자원을 선점할 수 없는 조건으로 누군가가 자원을 가지고 있을때 뺏을 수 없습니다. 위의 예에서도 지극히 정상적인 상식에서 A와 B는 물건을 불법적으로 훔칠 수 없죠.
4. 순환(환형) 대기 (Circle wait)
프로세스가 여러개 있을때 자원의 점유와 요청의 관계가 순환되는 조건을 말합니다. 프로세스 3개를 각각 p1, p2, p3라고 할때 p1이 p2의 자원을 요청하고, p2가 p3의 자원을 요청하고, p3가 p1의 자원을 요청하고 그 자원은 요청한 자원을 얻을 수 있을때까지 반환할 수 없습니다. 이 조건은 위의 세 조건이 만족이 되면 자연스레 만족됩니다.
아래의 그림은 순환 대기를 나타낸 것인데 R1, R2, R3는 각 P1, P2, P3가 점유한 자원을 의미하고 각 자원은 소유된 프로세스쪽으로 화살표가 나가고 있습니다. 프로세스들의 화살표는 요구하는 자원쪽으로 향하고 있지요. 여기서 이 화살표들의 방향대로 나가면 사이클을 그리며 무한정 순환하게 됩니다.
교착상태 발생 코드
아래의 코드는 교착상태를 스레드를 이용해서 발생한 예제입니다. 여기서는 간단히 메인 스레드와 메인 스레드에서 생성된 스레드를 고려합니다.
(정확히 100% 아래처럼 동작한다고 말할 수는 없지만 위 코드는 대부분 제가 의도한 대로 동작합니다. 왜냐면 스레드 실행 순서의 흐름은 예측할 수 없으니까요.)
코드를 보면 각각 critical section을 pthread_mutex_lock을 통해 보호하고 있지요. 이 소스 코드에서 눈 여겨 봐야할 점은 메인 스레드가 우선 lock1을 점유하고, 이후 생성된 스레드는 lock2를 점유하고 있습니다.
바로 메인 스레드는 lock2를 점유하여 임계 영역에 진입하여야하는데 이미 생성된 스레드가 가지고 있습니다. 그렇기 때문에 lock2가 해제될때까지 기다립니다.
한편 생성된 스레드는 lock2는 점유했고 lock1을 얻은 후에 임계 영역을 진입하려합니다. 하지만 lock1은 이미 메인 스레드에 의해 점유되었습니다. 따라서 lock1이 해제될때까지 기다려야합니다.
여기서 위의 네가지 조건이 보이시나요?
첫번째 조건으로 critical section으로 특정 한 스레드만 임계 영역에 진입할 수 있습니다. 여기서 임계 영역이 중첩되어 있네요. lock1을 이용해 잠근 임계영역과 lock2를 통한 임계영역이지요.
두번째 조건으로 점유와 대기를 이루고 있습니다. 메인 스레드는 lock1을 점유하고 lock2가 해제되기를 기다리고 있고 생성된 스레드는 lock2를 점유하고 메인 스레드에 의해 lock1이 해제될때까지 기다리고 있습니다.
세번째 조건은 비선점 조건인데 서로 강제로 lock1, lock2를 가져올 수 없습니다.
네번째 조건은 위의 그림처럼 그려보면 사이클을 이루게 되죠.
이 프로그램의 실행결과는 어떨까요? 무사히 수행이 될까요? 실행시켜 보도록 하겠습니다.
# ./a.out main thread start and create thread thread start
네, 사용자가 중지하지 않는 이상 이 프로그램은 영원히 끝나지 않습니다. 아래에서 교착상태를 해결하는 코드를 볼 수 있습니다.
교착상태 문제 해결
교착상태가 발생하면 어떻게 해결할 수 있을까요? 크게 세가지 방법이 있는데 간략히 알아보도록 합시다.
1) 교착 상태 예방
교착 상태가 발생하기 전에 조치를 취하는 방식으로 위의 4가지 조건 중 하나라도 발생시키지 않으면 됩니다.
- 자원의 상호배제 조건 방지
- 점유와 대기 조건 방지
- 비선점 조건 방지
- 순환 대기 조건 방지
앞서 얘기했듯이 처음 세조건이 만족되면 순환 대기가 발생하므로 결국에는 순환대기를 발생시키지 않으면 됩니다.
교착 상태 예방은 시스템의 처리량을 떨어뜨립니다.
2) 교착 상태 회피
예방처럼 교착 상태의 발생 가능성을 미리 제거하지 않고 교착상태가 일어난다고 보고 이것을 회피하는 것입니다. 예를 들어 몇 초동안 프로세스나 스레드가 임계 구역 전에 대기 중이라면 이 시간이 지난 후 다음 작업을 수행합니다. 위의 예제코드를 아래와 같이 수정할 수 있습니다.
이제 아래와 같이 컴파일하도록 해봅시다. 아래 옵션 중에서 -L은 라이브러리가 위치한 디렉토리를 명시해주고 -lcalcops는 우리가 작성한 lib파일 이름입니다. 우리가 lib파일 이름을 libcalcops.a라고 정의하였을때 옵션은 -lcalcops라고 명시해주면 됩니다. lib가 -l로 바뀌고 확장자 .a를 빼주는 식이죠.
그렇다면 아까와 같이 /usr/lib/libcalcops.so파일을 삭제하고 위 프로그램을 다시 실행해보도록 해봅시다.
# ./a.out ./a.out: error while loading shared libraries: libcalcops.so: cannot open shared object file: No such file or directory
정적라이브러리와는 다르게 라이브러리 파일이 삭제되면 실행 프로그램도 실행되지 않네요. 우리가 추측해볼 수 있는것은 프로그램이 시작시에 적재되는 것을 의심해볼 수 있고 실제로도 그렇습니다.
왜 하필 /usr/lib에 파일을 복사했을까요? 공유라이브러리할때 /etc/ld.so.conf설정파일로부터 라이브러리 파일이 있는 지 봅니다.
제 시스템을 기준으로 설명드리면 아래와 같이 /etc/ld.so.conf 파일이 작성되어 있습니다.
include /etc/ld.so.conf.d/*.conf
다시 /etc/ld.so.conf.d/ 디렉토리를 보면 libc.conf와 x86_64-linux-gnu.conf 파일 두개가 존재합니다. 하기의 경로에 lib파일이 있으면 실행되며 추가적으로 경로를 추가 후 저장하고 나와서 ldconfig 명령을 주어 반영하면 됩니다.
백그라운드에서 실행되는 일종의 프로세스이며 오랫동안 유지되는 프로세스입니다. 주로 시스템이 시작될때 같이 시작이 되서 시스템이 종료될때 같이 종료되는 경우가 대부분입니다. 백그라운드에서 돌면서 사용자가 원하는 서비스를 제공하는 프로세스를 의미합니다. 데몬은 일반 백그라운드 프로세스와는 다르게 제어 터미널이 없고 표준 입출력이 없습니다. 즉, 사용자로부터 직접적인 입력를 받지 않고 출력도 하지 않습니다.
데몬의 예는 뭐가 있을까요? 관례상 데몬은 프로세스 이름이 d로 끝납니다. 예를 들어 네트워크 인터페이스들을 감시하고 서버로부터 요청을 감지하는 inetd, 설정된 날짜와 시간이 되면 지정된 명령을 실행시켜주는 cron, 보안 원격 로그인을 제공하는 sshd와 같은 것들이 있지요. 이러한 데몬을 생각해보세요. 사용자가 입력하지도, 출력을 내보내지도 않습니다.
1. 데몬이 파일을 생성할때 적절한 권한이 있어야하는데 umask를 사용하여 파일 모드 생성 마스크를 설정합니다. 보통 0입니다. umask는 생성되는 파일의 생성모드를 정해주는데 umask가 현재 0x022라면 group의 w, other의 w 권한을 주지 않는 다는 것입니다. 따라서 파일을 rwxrwxrwx로 생성한다 하더라도 rwxr-xr-x로 생성이 된다는 겁니다.
데몬이 파일을 올바르게 생성해야만 하므로 umask(0)을 지정합니다.
2. 자식 프로세스를 생성(fork)한 후 부모 프로세스는 종료(exit)합니다. 이렇게 자식 프로세스보다 부모 프로세스가 먼저 종료되면 initd이 자식 프로세스를 넘겨받게 됩니다. 자식 프로세스는 세션의 리더가 아니며 프로세스 그룹 리더도 아닙니다.
3. setsid를 호출해서 새로운 세션을 생성합니다. setsid는 다음과 같은 세가지 일이 일어납니다.
#include <unistd.h> pid_t setsid(void);
호출 프로세스가 프로세스 그룹 리더가 아니면 새 세션을 생성합니다. 1. 호출 프로세스가 새 세션의 리더가 됩니다. 2. 호출 프로세스는 새 프로세스 그룹의 리더가 됩니다. 3. 호출 프로세스는 제어 터미널이 없습니다.
4. 현재 작업 디렉토리를 루트 디렉토리로 변경합니다.
5. 필요하지 않은 모든 파일 디스크립터를 닫습니다. 어떻게 열려진 파일 디스크립터를 모두 알 수 있을까요? getrlimit이나 open_max를 사용하면 됩니다.
6. 표준 입출력을 모두 /dev/null로 지정합니다. 데몬은 사용자로부터 입력을 받을 필요가 없고, 출력, 에러를 직접 하지 않습니다. 데몬에는 제어 터미널 장치가 연관되어 있지 않고 출력이 표시될 대상이나 사용자로부터 입력을 받을 통로가 없습니다.
- ndfs : 감시할 파일 디스크립터의 갯수인데 마지막 파일 디스크립터 번호에 +1을 지정해줍니다. fd_set은 배열인데 배열의 index는 0부터 시작하므로 +1을 해줍니다. 아래에서 설명하도록 하죠.
- readfds : 읽을 데이터가 있는지 감시하는 파일의 집합입니다.
- writefds : 파일에 데이터를 쓸 수 있는지를 검사하기 위한 파일 집합입니다.
- exceptfds : 파일의 예외 사항이 있는지 검사하기 위한 파일 집합입니다.
- timeout : 데이터의 변화를 감지하기 위해서 사용하는 time out 값입니다. 예를 들어 readfds에 지정된 시간동안 읽을 데이터가 없다면 select는 0을 반환합니다. 만약 timeout을 NULL로 지정하게 되면 무기한으로 대기하게 됩니다.
성공시 fd의 갯수를 반환합니다.
fd_set
fd_set은 아래와 같은 비트 배열입니다. 총 1024개의 파일을 감시할 수 있죠. 아래는 readfds를 표현했습니다. fd_set은 총 1024개의 fd를 감시할 수 있습니다.
우리는 readfds에 0, 1, 2, 3을 read할 데이터가 있는지 감시한다고 해봅시다. 이때 fd 3에 read할 데이터가 있다면 위의 배열은 아래와 같이 fd 3에 해당하는 비트 배열의 flag가 1이 세팅됩니다. 이렇게 하여 fd 3에 읽을 데이터가 있는지 알 수 있습니다.
이때 fd 3은 마지막에 열었던 파일 디스크립터입니다. 그렇다면 지금까지 감시해야할 fd의 갯수는 몇개인지 나오지요? 마지막 fd의 번호 + 1이 됩니다. 그 때문에 nfds가 마지막 fd의 +1이 되는 것이죠.
그래요, 이제 fd를 감시하는 것은 알겠는데, 이것이 스레드처럼 동시에 fd를 관리할까요? 결론은 아닙니다. 만약 파일을 1024개를 관리하고 1023번 파일디스크립터(fd)에 read 플래그가 1이 되었다면 select는 모든 fd_set을 모두 검사하여 변화가 있는 fd를 알아내게 됩니다.
FD 매크로
보다 파일 디스크립터 집합(fd_set)을 관리하기 편하게 하기 위해서 리눅스에서는 다음과 같이 4개의 매크로를 지원합니다.
매크로
설명
voidFD_CLR(intfd, fd_set *set)
set에서 fd를 제거합니다. 더 이상 검사하지 않습니다.
int FD_ISSET(intfd, fd_set *set)
set에 fd의 플래그가 setting되었는지 확인합니다.
voidFD_SET(intfd, fd_set *set)
set에 fd를 추가합니다. 이것은 flag를 setting하는 것이 아닌 검사를 하겠다는 뜻의 setting입니다.
voidFD_ZERO(fd_set *set);
set에 있는 fd를 전부 제거합니다.
이 select를 활용하여 파일 3개로부터 입력이 들어오면 출력해주는 일종의 서버 프로그램을 작성해보도록 합시다. 여기서 나오는 select와 poll은 정규파일에서 다루는 것은 적절하지 않습니다. 이러한 함수들은 정규 파일을 감시하게 되면 바로 준비되었다고 판단하여 return 되어 버리기 때문입니다. 정규 파일은 언제나 디스크에서 읽을 준비가 되어있습니다. 그래서 디스크 파일보다는 네트워크 입출력에서 다루는 것이 적절합니다.
여기서는 네트워크라는 개념은 배제하고 오직 상큼하게 select의 개념을 살펴보고 어떻게 다뤄보는지 느낌만 가져가 봅시다.
예제
//multiIO_select.c
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define FD_SIZE 3
#define BUF_SIZE 8
int main(int argc, char *argv[]){
int i, n, ret, fd_count, end_count = 0;
int fds[FD_SIZE];
char buf[BUF_SIZE] = {0,};
struct timeval tv;
fd_set retfds, readfds;
//3개의 파일의 이름을 받는다.
if(argc != 4){
printf("usage : %s file1 file2 file3\n", argv[0]);
return 0;
}
//readfds를 공집합으로 만든다.
FD_ZERO(&readfds);
for(i = 0; i < FD_SIZE; i++){
fds[i] = open(argv[i+1],O_RDONLY);
if(fds[i] >= 0){
//readfds 집합에 파일디스크립터 fds[i]를 추가한다.
FD_SET(fds[i], &readfds);
//맨 마지막 파일열린 파일 디스크립터가 가장 큰 값을 갖는다.
fd_count = fds[i];
}
}
while(1){
//readfds를 retfds로 옮기는 이유는 select를 통해서 readfds가 바뀔 수 있다.
retfds = readfds;
//열린 파일 디스크립터의 최대값 +1
ret = select(fd_count + 1, &retfds, NULL, NULL, NULL);
if(ret == -1){
perror("select error ");
exit(0);
}
for(i = 0; i < FD_SIZE; i++){
//만약 fds[i]에 대해서 읽을 준비가 된 파일 디스크립터이면
if(FD_ISSET(fds[i], &retfds)){
//읽고 출력해준다.
while((n = read(fds[i], buf, BUF_SIZE)) > 0){
//quit가 들어오면 fds[i] read 종료
if(!strcmp(buf,"quit\n")){
//readfds에서 지우고
FD_CLR(fds[i], &readfds);
//파일디스크립터 close
close(fds[i]);
end_count++;
if(end_count == FD_SIZE)
exit(0);
continue;
}
printf("fd[%d] - %s",fds[i], buf);
memset(buf, 0x00, BUF_SIZE);
}
}
}
}
}
위의 코드는 아주 간단합니다.3개의 파일에 대해서 읽을 준비가 된 파일 디스크립터가 있으면 해당 파일을 읽어서 출력해주는 프로그램입니다.
FD_ZERO를 이용해 readfds를 초기화 시킨 후 readfds에 감시할 파일 디스크립터를 추가합니다. 그것이 FD_SET입니다. readfds의 복사본인 retfds를 select 시스템콜을 통해서 감시를 시작합니다. 만약 복사본을 넣지 않고 쌩으로 readfds를 넣는다면 select함수는 readfds를 단순히 조회만 하지 않고 변경합니다. 이러한 변경은 불필요합니다. FD_ISSET을 통해서 현재 준비된 파일디스크립터를 처리한 후에 다시 변경되지 않은 readfds로 돌아가야합니다.
만약 retfds에 flag가 on이 되면 FD_ISSET이 true를 반환하게 됩니다. 그러면 파일에 업데이트 된 내용을 출력해주게 됩니다. 결론적으로 위 소스 코드는 파일을 읽었다고 끝내지 않고 계속 파일에 쓰인 내용을 출력해주는 프로그램입니다.
이를 시험해보기 위해서는 다른 프로그램이 필요합니다.
//fwrite.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]){
int fd, ret;
char buffer[32];
if(argc != 2){
printf("usage : %s file \n", argv[0]);
exit(0);
}
//파일을 쓰기 전용으로 연다.
fd = open(argv[1], O_WRONLY|O_APPEND);
if(fd < 0){
perror("file erorr ");
exit(0);
}
while(1){
//한줄 처리를 위해서 fgets를 사용
fgets(buffer, sizeof(buffer), stdin);
//파일에 기록
ret = write(fd, buffer, strlen(buffer));
if(ret < 0){
perror("write error ");
exit(0);
}
//quit문자열은 종료
if(!strcmp(buffer, "quit\n")) break;
}
close(fd);
return 0;
}
이 실행 파일은 단순히 argv[1]로 들어오는 파일명에 기록하는 프로그램입니다. 이제 두 소스 코드를 컴파일하고 쓰일 실행 파일을 생성합니다.
# gcc multiIO_select.c
# gcc fwrite.c -o fwrite
# touch a b c
이제 터미널 총 4개를 띄어서 테스트해볼건데, 하나는 select함수를 사용하는 프로그램, 다른 3개 터미널에는 fwrite를 수행하는 프로그램으로 select를 실험합니다. 단, 같은 디렉토리에 있어야합니다.
./a.out a b c
# ./a.out a b c fd[3] - writing fd[3] - a... fd[4] - writing fd[4] - b... fd[5] - writing fd[5] - c... fd[3] - hello fd[4] - world fd[5] – bye #
./fwrite a
./fwrite b
./fwrite c
# ./fwrite a writing a... hello quit #
# ./fwrite b writing b... world quit #
# ./fwrite c writing c... bye quit #
위와 같이 출력이 더럽게 나오는 것은 BUF_SIZE가 작아서 그렇습니다. BUF_SIZE를 늘리게 되면 위와 같은 지저분한 출력을 예방할 수는 있습니다.
MultiIO_select.c에서는 select를 이용해서 여러 파일 디스크립터의 내용들을 입력받아서 출력해주고 있습니다. 설명드렸듯이 select는 정규파일에 대해서 항상 준비되어있기 때문에 곧 장 return되어 버립니다. 티는 나지 않지만 multiIO_select.c의 select함수 호출 이후 printf로 아무 로그나 찍으면 엄청나게 루프를 도는 것을 확인할 수 있습니다. 여기서는 이해를 돕기 위해서 만든 프로그램일 뿐입니다.
우선 기억은 나지 않으나 고등학교 1학년때 친구에게 똥침을 맞고 그자리에서 진짜 통곡했다. 진짜 죽이고 싶었지만 빵셔틀인 나는 참을 수 밖에 없었다. 이때 내 항문에 상처가 났나? 사실 이게 발단인지는 모르겠다.
언제부터 기억이 나냐면 군대에서 똥쌀때 뭐가 좀 튀어 나와서 치질이구나 했는데 통증이 없으니 그냥 참고 살았다. 근데 이게 점점 심해진다.. 제대하고 나서 똥을 싸면 피가 흥건하게 나오고는 했는데, 휴지가 지혈할 정도로 피가 나왔다. 사실 병원가서 항문까기가 얼마나 두려운가...
우선 인터넷에 여러분들처럼 "치질 증상", "치질 극복", "수술없이 치질 치료", "치질과 항문암"을 검색해서 나의 머신 러닝과 빅데이터 분석 결과 좌욕을 조지면 조금 낫는다고 하드라.
신기하게도 좌욕을 하고나니 피는 안났다. 그래서 계속 참았고 세월을 흘러보냈다.
그런데 이게 피는 안나와도 돌출하는 거는 들어가지 않았다. 걷기가 불편할 정도였다.
22살때 치질 초기 증상이 있다가 회사에 입사한 후 30살때까지 참았는데 30살때 병원을 간 이유가 똥 싸고 나서 진물이 새어나와서 이대로 가다가는 기저귀차겠다 싶어서 허겁지겁 병원으로 갔다. 30살에 기저귀라니...
검사
사실 남앞에서 똥구멍까는게 얼마나 수치스러운가. 하지만 내 나이 서른에 기저귀 차느니 눈 딱 감고 까는게 낫겠다 싶었다. 우선 간호사와 의사는 하루 왠종일 보는게 항문이니 날 그냥 생물학적 인간으로만 볼뿐 창피한 분위기는 아니다. 심지어 여러분과 눈을 잘 안마주친다. 사실 내가 눈깔고 있었다. 내 얼굴을 처다보지 않으니 수치심이 줄어든다. 더군다나 거기에 있는 환자들 모두 본인과 같은 증상으로 찾아온 나의 동지들이다.
진료실에서는 "의사 선생님이 항문을 좀 봅시다" 해서 그 옆에 있는 검사실에서 옆으로 누워서 항문을 보여준다. 간호사는 가운데 구멍이 있는 녹색 천으로 내 엉덩이를 가리고 의사는 나의 항문을 손가락으로 휘젓는다. 쑤셔! 막 쑤셔! 존나 쑤셔!!! 일로 갔다 절로갔다 앞으로 갔다 뒤로갔다
와... 이건...
가... 가버렷!!
한번도 경험해보지 못한 아픔이다. 게다가 괄약근에 힘도 풀린다. 항문으로 느낀 의사 선생님 손가락은 차가우면서 옹골졌다. 그렇게 손가락만 넣어도 치질 질환 중 어떤 질환인지 얘기해주는데, 역시 엉덩이 전문가이다. 나는 치핵 4기 판정을 받았다.
나는 이전에 빅데이터 분석을 진행했고 나의 증상이 치루인것이라고 생각했다. 변을 보고 난 이후에 진물과 같은게 나오니까 항문 주위에 길이 있다고 느껴졌기 때문이다. 치루라는 질환은 항문에 길이 만들어져 그 길로 액이 세어나오기 때문에 악취가 나고 수술만이 유일한 방법으로 괄약근을 잘라내기 때문에 항문 질환 중에 최악으로 알려져있다. 물론 재발할 수 있다.
난 일단 불행 중 다행으로 치핵 4기 판정이고 이것도 무조건 수술밖에 답이 없다. 난 바로 그 주 금요일에 수술을 예약했고 토,일 이틀을 입원하도록 했다.
수술 전
수술하기 전에 주의 사항을 주는데 대충 아래 사진과 같다. 난 만일을 대비해 아침도 먹지 않았다.
그리고 중요한거는 물을 그 당일에 절대 먹지 않는 것을 추천한다가 아니라 먹지말자.
나도 후기를 찾아보던 중 물을 절대 먹지 말라는 조언을 들었다. 그냥 액체는 이 세상에 없다고 생각하라. 마취를 하반신 마취를 하는데 마비되어 오줌이 나오지 않는다. 그럴때 호스를 요도에 집어넣어 오줌을 빼는데 진짜 극악으로 아프다고 하니 절대 당일에 물먹지 않도록 하자.
생각만해도 아프다.
이 사항만 잘 지키고 병원을 가면 친절한 간호사가 수술과정에 대해서 설명해준다.
결론은 째고 지지고 너의 똥구멍은 치핵 4기 판정을 받았으니 존나 아플것이고, 그것을 인내해야하며 수술 후 약과 좌욕을 많이 조지는 것이 고통을 줄일 수 있는 유일한 방법이니 알아서 똥구멍 관리 잘하라는 것이다.
자, 설명이 끝나면 환자복으로 갈아입고 관장을 한다. 내 경우에는 항문에 관장약을 넣고 5분을 참고나서 볼일을 봐야하는데, 진짜 어떻게 5분을 참지? 간호사도 5분 참는 거는 어려우니 3분은 꼭 참으라고 했다. 3분은 커녕 내 괄약근이 약한 건지 1분 30초만에 항문이 열렸다. 역시 급똥은 당해낼 장사가 없드라. 5분 참으신 분은 무슨 일을 해도 대성할 분이다. 나는 이 사건을 계기로 매일 두시간동안 괄약근 운동을 하고 있다. 관장하고 나면 초음파검사를 하는데, 긴 막대기인 최첨단 장비를 통해서 더 정밀한 진단을 했다. 결론은 치핵4기인것은 변함이없었다. 의사선생님은 치루길을 검사하는것이라고 하였다.
수술
이제 내 똥구멍을 지지고 볶을 시간이왔다. 막상 수술은 진짜 너무 별거 없다. 새우자세로 누워있으면 의사선생님이 뒤에서 마취해주시는데척추에 주사 놓는다고 해서 쫄아 있었는데 뭐 이건 주사 놓은건지 안놓은건지 알 수 없을 정도로 통증이 없다. 마취에 대해서는 걱정안해도 되겠다.
그렇게 이제 하반신에 감각이 점점 없어지면서 수술에 들어간다. 그냥 너무 평온하게 클래식 음악이 틀어져있고 의사 샘도 간호사와 이런 저런 얘기를 주고받으면서 여유롭게 수술하신다. 역시 엉덩이 전문가님께서 안락하게 수술하셔서 심지어 난 졸았다.
뭐로 지지는지 오징어 탄내도 나고 자르는 소리도 난다.
도중에 간호사가 사진을 찍는데 이렇게 능욕 당하는 건가 싶었는데 알고보니 나중에 비교 사진을 보여주려고 하는 것이라고 한다. 그렇지만 뒤에서 카메라 셔터 터지는데 건물에서 뛰어 내리고 싶드라. 근데 다리가 안움직여서 뛰어내리지도 못한다.
그렇게 20분지났던가? 수술은 그대로 끝났고 항문에 거즈를 대주고 끝난다. 하반신이 안움직이니까 간호사가 바지를 입혀주는데, 엎드려 있는 상태에서 엉덩이만 살짝 들면 간호사가 바지 입혀준다.
난 또 돌아누운 상태에서 내 크라켄도 보여줘야하는건가 싶었는데 환자의 수치심을 줄여주는 간호사 누님한테 감동했다.
수술 후 당일
이제 인내와 고통의 싸움을 함께할 시간이다. 수술후 최소 6시간은 그냥 누워있어야한다. 고개도 들면 안된다. 척추마취후에 혈압이 낮아져 기절할 수도 있다고 한다. 간호사 누나는 병원에 입원할 동안은 그냥 시체처럼 누워있으라고 추천한다.
혹시 여러분이 핸드폰 거치대가 있다면 가져오는걸 추천한다. 누워서 핸드폰 보는게 여간 빡쎈게 아니다. 이때는 똥구멍보다 팔이 더아프다.
통증은 거의 없다. 무통주사를 달고 있기 때문이다. 그때까지만 해도 "아 뭐야 완전 개 ㅈㅂ이었잖아?" 라고 생각했는데, 무통 주사 효과가 거의 다할때 무통 주사를 개발한 사람한테 존경심이 바로 생기게된다. 무덤까지 찾아가서 절할뻔
진짜 너무 아프다. 고등학교 1학년 똥침의 고통이 밤새도록 지속적으로 이어진다. 바로 무통 주사 리필했다.
발기도 안된다. 신기해서 발기하려고 노력해봤는데 절대 안된다. 내가 고자인가 싶었다. 첫 소변은 난 12시간이 지나서야 볼 수 있었다. 그 전까지는 아무리 노력해도 나오지 않는다. 12시간 고자체험이라고 보면 된다.
입원 생활
의사가 아침에 회진을 돌고 내 똥구멍의 상태를 점검한다. 아침 점심 저녁 모두 병원밥을 먹고 30분 후 약과 식이 섬유를 같이 먹는다. 이제부터 좌욕을 조져야한다. 3시간에 한번이었던가 그렇게 좌욕을 주기적으로 해야한다. 마지막에는 거즈로 압박한다.
하루가 지나면 이제 슬슬 신호가 올텐데 신호가 오는 즉시 그냥 화장실에서 대기하는 것이 좋다. 진짜 괄약근에 힘이 없어서 자동문이다. 이때까지만 해도 무통주사 때문에 변볼때 통증이 없고 식이섬유때문인지 변이 굉장히 무르다.
입원할때 될수 있으면 하루 종일 누워있어야한다. 핸드폰을 계속들고 있어 똥구멍도 아프고 팔도 아프다.
퇴원
퇴원할때 무조건 무통주사를 리필하고 가도록 하자. 간호사가 주사 빼는 방법과 알코올 솜을 주는데 그걸로 지혈하면 된다. 나같은 경우에는 주사 잘못빼서 피가 흥건히 떨어졌다. 피를 보니 너무 무서웠다.
무통주사 뿐만 아니라 병원에서 먹었던 식이섬유, 변이 잘 나오지 않을때 넣는 약(난 쓰지는 않았다), 거즈, 약, 좌욕대를 같이 준다. 다 챙겨서 퇴원하도록 한다.
보험다되니 나중에 청구하도록 하자.
수술 전, 수술 후 그리고 입원과정에 대한 요약본은 사진을 참고하자.
퇴원 후 일상생활
이제부터 2주동안은 지옥과 같다. 무통주사 효과가 떨어지고 처음으로 볼일 볼때 그 고통은 잊을 수 없다. 난 내 몸에서 철수세미가 나오는줄 알았다.
약과 식이섬유는 꼭 챙겨먹고 좌욕도 자주해주어야한다. 수술후 4일 정도후에는 병원가서 상태를 점검해야하는데 이제 나의 모든 것을 보여주었으니 수치심도 없다. 이때쯤 되면 진료실은 바지 벗으면서 들어간다.
또한 술은 절대 한달 동안은 먹으면 안된다. 난 술을 너무 좋아하는 애주가이기 때문에 10일만에 소주를 먹었다. 내가 생각해도 제정신이 아니다.
맵거나 짠 음식은 먹으면 안되는데, 불닭볶음면 먹고 항문에서 용암이 흘러나왔다. 맵고 짠거는 먹지 말자.
그리고 난 모든 신경이 항문에 집중되어 있는 줄 몰랐다. 움직이거나 말할때, 기침할때 특히 괄약근을 그렇게 쓰는 줄 몰랐고 웃을때도 괄약근이 같이 웃는다. 똥구멍 세끼야. 넌 웃지만 난 운다. 원래는 기침할때 '에이취!!!!'라고 할때 수술하고 난 이후의 기침 소리는 '에취 으으......ㄺㄺ,'이라고 보면 된다.
그리고 여러분, 잘때도 항문을 쓰는 것을 아는가? 내가 자면 항문도 같이 자야하는데 이 세끼는 자질 않는다. REM 수면에 들어서면 똥구멍이 찔끔찔끔 움직이는데 아파서 깨게 된다.
즉, 항문이 태양계의 중심이자 우주의 중심이다.
화장실에서 볼일 볼때는 비데는 쓰면 안된다.아프기도 하거니와 상처가 잘 낫지 않기 때문인데 닦을때는 휴지에 물을 조금 묻히고 난 후에 닦았고 변 보고 바로 좌욕을 했다.
혹시 회사나 학교를 간다면 휴가를 이틀 쓰는 것을 추천한다. 회사나 학교에서 좌욕을 할 수는 없으니 말이다. 그리고 잘 걸을 수가 없다 아파서..
항상 시간이 약이다. 2주가 지나니 이제 괄약근에 힘도 들어가고 3주가 넘어갈 무렵 피와 진물이 나오지 않는다.
나는 수술 후 병원에 2번만 나가고 일도 바쁘고 해서 나가지 않았다. 덕분에 수술때 사진찍은 내 항문을 보지 못했다. 그 사진 삭제좀....
아마 이글을 보는 여러분은 치질의 고통을 앓고 있을 것이고 병원에서 수술할 지 고민중일텐데 난 빨리 수술하라고 권하고 싶다. 아까 얘기했듯이 거기 있는 환자들은 모두 당신과 같은 동지들이고 의사나 간호사는 하루 종일 보는게 항문이라서 당신을 질병을 앓고 있는 생물학적 항문으로 보기 때문에 수치심 들 필요가 없다.
심지어 내가 다니는 병원에는 여성의 비율이 훨씬 많았다.
그리고 지금 이 글을 다시 쓰는 시점에는 이 글을 올린지 대략 두달이 되어가는 시점인데, 내 블로그의 키워드를 공개하겠다.
이 글은 컴퓨터 개발 블로그인데, 치질 키워드가 90%이다. 그 만큼 앓는 사람이 많다는 것이다. 근데 치질 4기 사진은 왜 찾는건지는 모르겠다. 개발때려치고 치질 전문 블로그로 바꿔야되나.