www.acmicpc.net/problem/2839

 

2839번: 설탕 배달

상근이는 요즘 설탕공장에서 설탕을 배달하고 있다. 상근이는 지금 사탕가게에 설탕을 정확하게 N킬로그램을 배달해야 한다. 설탕공장에서 만드는 설탕은 봉지에 담겨져 있다. 봉지는 3킬로그

www.acmicpc.net

 

 

 

문제 설명

설탕 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만 생각하고 있었는데 다른 사람의 해답을 보니 이러한 방법이 있다는 것을 알았습니다. 역시 저빼고 다른 사람들은 똑똑한것 같습니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

적성검사가 끝나고 2주후에 적성 검사 결과가 나온다. 적성검사 개판쳤던것에 비해 의외로 합격하니 의아했다.

LG전자 면접은 1, 2차로 나뉘는데 1 면접 전에 lg사이트에 TO-DO 것이 생긴다.

5 정도로 PPT 주제에 맞게 채워야 하는데, 번째 주제는 본인이 가장 좋아하는 전공을 5 정도로 기재하고 좋아하는지를 설명한다. 번째 주제는 장은 진행했던 프로젝트, 마지막 주제는 자신을 어필하는 주제였다.

 

이것을 ppt 준비하여 기간내에 제출하여야 한다. ppt 디자인에는 살짝만 신경쓰고 나머지는 내용에 치중하도록 하는 것이 좋을 같다. 그리고 ppt 대한 장수는 중요하지 않다. 나는 5장만 채우지 않고 썼다.

 

면접 장소와 면접 가는

같은 경우 오후 1 정도에 면접이었는데 면접 장소는 마곡 LG사이언스 파크 이다.

지하철이 급행 9호선을 타고 가야 한다. 일반 열차를 타고 가면 급행 열차를 보내기 위해 가양역에 한참 머물러있기 때문에 예상했던 것보다 시간이 걸린다.

일반 열차를 타고 가면 신논현에서 마곡나루역까지 40분이 걸린다. 그래서 지각할뻔했다. 급행은 25분정도면 도착한다.

 

면접 보기 코딩

도착하게 되면 인사팀 직원이 보안 문열고 들여보내준다. 면접을 지하 1층에서 보게 되는데 면접을 보기 전에 코딩 문제를 풀게 되는데 그렇게 어렵진 않은 수준으로 C언어를 배웠다면 있는 정도이다.

시간은 40 정도이고 대략 10문제 정도이다. 시간이 되면 인사팀 직원이 종이를 가져간다.

 

면접 복장

LG전자에서 면접 복장으로 자유로운 복장으로 착용하라고 하였는데, 주변 면접자들을 보니까 죄다 정장이었다. 물론 나도 정장입고 가는 것이 마음이 편했다.

정장을 입은 비율은 10 8명으로 정장이라고 보면 된다.

 

PT면접

이제 면접장으로 가면 나에 경우에는 면접관 2명이 앉아있었고 자리에는 노트북이 놓여있었다. 면접관의 경우 프로젝트 리더 한명, HR 사람 한명이 있었다. 면접관의 경우에는 자신에게 관심있는 면접관이 들어온다고 하고, 다른 면접자들에게 물어보니 어던 사람은 4명이 있었다고 한다. 노트북 화면에는 내가 준비했던 ppt 켜져있었다. 이제 ppt 앉아서 발표하면 된다.

 

중간에 영어로 자기소개하라고 하는데, 준비해온 자기 소개 영어를 하면 되는데, 중간에 면접관이 끊었다.

 

그리고 중간에 내가 손코딩 문제로 주제가 넘어갔다. 손코딩 면에서는 칭찬을 받았다. 불길했다.

 

마지막으로 면접이 끝나고 면접관이 내게 악수를 요청하면서 이런 말을 남겼다.

"행운을 빌어요"

불길했다.

 

PT면접에서 면접관은 2명이고 면접자는 1명이며 면접은 30분이 넘게 진행되었다

 

면접 분위기

면접 분위기는 전혀 딱딱하지 않다. PT 발표도 면접관이 성의있게 들어주었다. 그리고 심지에 내가 대답을 못하니 면접관 자신이 웃으면서 설명해주기도 했다.

다른 면접자의 말도 들어보니 압박하는 면접은 없었다. 면접을 편한 분위기 속에서 진행되니 안심해도 같다.

 

면접이 끝나면 아까 인사팀 직원이 보안 게이트까지 데려다준다. 내쫓는다. 상태로 다시 집으로 가면 된다. 면접비는 그날 주는 것이 아니라 1~2 계좌로 입금이 된다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

면접장까지 가는 길

코딩 테스트가 끝난 2~3주가 지나게 되면 면접을 보게 된다.

이전에는 코딩 테스트에서 만점을 받으면 가산점이 있다고 했지만 2018 내가 면접을 당시에는 그런 가산점 같은게 없다고 했다. 실제로 가산점이 있는지 없는지도 모르지만, 있으면 좋겠다고 생각했다.

냐면 맞았다고 생각했기 때문이다.

워낙에 회사다보니까 면접 날짜가 직무마다 다르고 최대 2주간에 걸쳐 모든 사람의 면접이 끝난다. 나같은 경우 삼성 SW DS쪽으로 지원하였고, 거의 마지막에 걸려있었으니 면접을 준비하는데에는 충분한 시간이다. 결국은 나가리

 

나는 2018년에 면접을 봤는데 새벽 6 30분까지 양재역으로 총집합을 한다.  사실 6 30분인지 확실하지는 않으나 엄청 일찍 가야하는 것은 맞다.

버스는 큰 버스로 5대 정도로 가는데 도착하는 순간 담배를 못핀다. 안그래도 면접이 하루에 3개를 몰아서 하는 면접이라 체력과 정신력이 중요하다. 버스 타기 전에 5대 정도 줄담배로 니코틴 충전하도록 하자.

 

도착하면 건물 안에서 이름표를 나눠주고 짐을 둔다. 그리고 조를 편성한다. 이제 밖으로 나갈 없다.

면접은 3개의 면접을 보는데, 순서는 사람마다 로테이션을 돌기 때문에 어떤 면접을 먼저 보는지는 없다.

 

 

같은 경우에는 번째 창의력 면접, 번째 인성면접, 번째 직무면접이었다.

창의 면접

창의력 면접에서는 용지를 나눠주고 컴퓨터 앞에 앉아 화면에 주어진 문제에 대해서 50 동안 아이디어를 생각해 컴퓨터에 쓰고 용지에도 아이디어를 써야한다. 그리고 아이디어를 가지고 면접관과 소통하며 발표한다. 약간 면접이라기 보다는 아이디어를 통해서 면접관하고 이야기하는 것으로 나머지 면접 보다는 분위기가 가볍다. 문제를 공개할 수는 없지만 키워드로는 대기 오염이었다.

면접관은 3명이고 면접자 1명이다.

 

인성면접

인성 면접 전에는 인성검사를 시작하는데 빨리 빨리 끝내야한다. 인성 면접 보기전에 면접실 앞에서 대기하는데 이때가 금단 현상과 함께 긴장이 극도로 심해져 사타구니에 땀이 찬다. 많은 사람들이 인성 면접이 가장 중요하다고 하는데 바로 최종 보스(높으신 분들,임원)들이 면접을 보기 때문이다. 다른 면접관들은 현직에 종사하는 사람이 면접관으로 나오는데 아무리 사람들이 점수를 좋게줘도 임원들이 거르면 면접 탈락이라는 소문이 많이 나돌았다. 면접관들은 인자한 척을 하면서 긴장을 풀어준다. 하지만 그런게 무서웠다. 한번 잘못하니 숨소리가 강풍이다. 면접 시간은 10~15 정도로 면접 시간이 끝나면 밖에서 직원이 시간이 되었다고 문을 두드린다.

같은 경우에 존경하는 인물이 "앨런 튜링"인지 물어보았다.

많은 면접 질문에 대비했다고 생각했지만 존경하는 인물을 물어볼지는 몰랐다. 왜냐면 구라쳤기 때문이다.

가장 어이도 없고 생각치도 못한 질문은 "앨런 튜링이 어떤 업적 가지를 말해보세요"

당연히 모른다. 그냥 이미테이션 게임에 주인공이라는 것밖에 모른다.

면접관은 3명이고 면접자는 1명이다.

 

 

그렇게 멘탈이 개박살나면서 나는 오전에 면접 2개를 마쳤다. 오후가 되면 이제 밥을 먹는데 역시 삼성전자 밥은 너무나도 맛있다. 오후 2~3시정도가 되면 이제 가져온 서류와 면접비를 받게 된다.

 

직무 면접

직무 면접 같은 경우에는 현직에 있는 사람이 면접관이다. 문제는 공개할 없으나 대략적인 키워드는 반도체, 정렬 알고리즘, 보안 정도이다. 3 정도의 문제가 주어지는데 문제를 A4용지에 풀고 들어가서 면접관 앞에서 발표하면 된다. 문제풀이시간은 대략 40 정도이다.

의외로 면접관과 나의 거리가 되게 좁고 심지어 나는 인사하다가 넘어질뻔했다.

그리고 앞에는 칠판이 있는데 풀어온 것을 설명할 칠판에 그림이나 글을 써서 설명할 있다. 나는 신나게 설명하다가 보드마카를 들고 나와버렸다.

보드마카는 놓고 오도록 하자.

직무면접의 면접관은 4명이고 면접자는 1명이다.

 

이제 면접이 끝나면 5 정도가 되는데 그대로 양재역에 내려준다. 맨탈이 박살 상태에서 양재역에서 집으로 버스타고 가면 된다.

 

면접 결과는 2주후에 발표하고 떨어졌다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

 

 

 

빌드 자동화 툴 Cmake에 관한 설명과 사용법을 알아보도록 하겠습니다.

 

Cmake 사용하는 이유

Cmake는 Makefile을 만들어주는 툴입니다.

Cmake 설명하기 전에 Makefile 무엇이냐면 빌드를 편리하게 해주는 일종의 빌드 스크립트라고 보시면 됩니다. 스크립트를 make명령을 사용해서 실행시키기는 겁니다.

그냥 shell파일을 이용해서 빌드를 있겠지만 Makefile 사용하는 이유는 Incremental build 방식을 사용하기 때문입니다.

 

Incremental build?

프로젝트의 규모가 커질수록 수많은 소스 파일들이 생겨날테고,  빌드하는 시간도 오래걸리게 됩니다. 우리가 대학교에서 프로젝트하는 수준은 그냥 강아지 애교 수준이고 정말 회사에서 일하게 경우에는 빌드하는 것도 시간이 걸립니다.

그래서 우선 처음에는 모두 빌드를 해놓고, 이후 수정된 파일에 대해서는 소스파일과 연관된 것들만 빌드하여 시간을 줄여주는 빌드 방식이라고 생각하시면 됩니다.

 

이러한 편리성이 존재하지만 Make 의존성에 관한 기술을 일일이 기재해야 되기 때문에 관리측면에서 번거롭습니다.

Cmake 이런 귀찮은 점을 해결해주는 툴로서 Makefile 생성해내는 역할을 합니다.

 

 

간단하게 Cmake 사용하는 방법을 알아보도록 하겠습니다.

a.c

int add(int a,int b){

    return a+b;

}

 

a.h

int add(int,int);

 

b.c

#include <stdio.h>

#include "a.h"



int main(){

    printf("%d+%d=%d\n",5,6,add(5,6));

    return 0;

}

 

 

a.c add라는 함수가 존재하고 단지 수를 더해서 결과를 return하는 함수입니다.

a.h a.c 함수를 선언한 헤더파일이고 b.c a.h 함수를 사용하는 실제 프로그램입니다.

간단한 프로그램을 빌드하는 CMakeLists.txt 작성은 줄이면 됩니다.

 

 

 

 

 

 

CMakeLists.txt

 

ADD_EXECUTABLE(a.out a.c b.c)

 

ADD_EXECUTABLE 실행할 파일을 생성하는 명령으로 번째 기재할 내용은 실행파일 이름이고 나머지는 실행파일을 생성하기 위한 source파일입니다.

 

 

이제 아래의 명령을 통해서 실행해보도록 합시다.

cmake CMakeLists.txt

 


#cmake CMakeLists.txt

-- The C compiler identification is GNU 5.5.0

-- The CXX compiler identification is GNU 5.5.0

-- Check for working C compiler: /usr/bin/cc

-- Check for working C compiler: /usr/bin/cc -- works

-- Detecting C compiler ABI info

-- Detecting C compiler ABI info - done

-- Detecting C compile features

-- Detecting C compile features - done

-- Check for working CXX compiler: /usr/bin/c++

-- Check for working CXX compiler: /usr/bin/c++ -- works

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

-- Detecting CXX compile features

-- Detecting CXX compile features - done

-- Configuring done

-- Generating done

# ls

a.c  a.h  a.out  b.c  CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  Makefile

 

그러면 소스파일 외에도 Cmake 관련된 파일이 생성되는데, Makefile 추가로 포함되었음을 있습니다.

만약 cmake 없었다면 Makefile 내용은 여러분들이 작성했어야하겠죠?

Cmake 이용하면 간단하게 줄이면 되니까 무척이나 편리합니다.

 

이제 실제 make 실행하면 우리가 만들어내고 싶은 a.out이라는 실행파일이 만들어집니다.


# make

[ 33%] Building C object CMakeFiles/a.out.dir/a.c.o

[ 66%] Building C object CMakeFiles/a.out.dir/b.c.o

[100%] Linking C executable a.out

[100%] Built target a.out

# ls

a.c  a.h  a.out  b.c  CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  Makefile

# ./a.out

5+6=11

 

Cmake 변수 선언도 가능하며 다양한 명령어가 존재합니다. 이런 명령어들은 , 소문자를 구분하지 않지만 보편적으로 대문자를 사용합니다.

 

 

 

 

CMAKE_MINIMUM_REQUIRED()

Cmake빌드를 실행할 최소 버전을 명시합니다. 보통 위에 사용하며 기재된 버전보다 낮다면 오류를 보여주고 빌드를 종료합니다.

사용법은 아래와 같습니다.

CMAKE_MINIMUM_REQUIRED( VERSION 3.4.3 )

SET() 변수 정의

SET명령어는 변수를 정의하며 다음과 같이 사용됩니다.

SET(변수명 )

어렵지 않죠? 뿐만 아니라 List형식으로도 변수를 정의할 수도 있습니다.

SET(변수명 1 2 3)

이렇게 정의된 변수는 $변수, 혹는 "{}" 써서 ${변수} 사용할 있습니다.

앞의 CMakeLists.txt 변수를 통해 바꿔본다면 이렇게 바꿀 있습니다.

 

SET(SRC_FILES a.c b.c)

SET(BIN_NAME a.out)

ADD_EXECUTABLE(${BIN_NAME} ${SRC_FILES})

 

PROJECT() 프로젝트명 정의

PROJECT(프로젝트 )으로 쓰이게 되고 프로젝트의 이름을 정해주는 명령어로 CMAKE_PROJECT_NAME이라는 변수에 저장이 됩니다.

 

 

MESSAGE() 메시지 출력

혹시 변수에 들어있는 값이라던가 오류 메시지를 표시하려면 MESSAGE라는 명령어를 사용하여 출력하면 됩니다.

MESSAGE( TYPE MESSAGE)

메시지 종류는 생략가능하며 5가지의 종류가 있습니다. STATUS 가장 심각도가 약한 단계이고 FATAL_ERROR 심각도가 가장 심합니다.

 

TYPE 설명
STATUS 상태 메시지 출력, 계속 진행
WARNING 경고 메시지를 출력, 계속 진행
AUTHOR_WARNING 프로젝트 개발자용 경고 메시지를 출력, 계속 진행
SEND_ERROR 오류 메시지를 출력, 계속 진행, Makefile생성하지 않음
FATAL_ERROR 오류 메시지를 출력, 작업 중단

 

 

ADD_LIBRARY()

ADD_LIBRARY 실행파일이 아닌 library파일을 생성할 쓰이는 명령입니다. 아래와 같이 쓰이게 되죠.

ADD_LIBRARY( library이름 TYPE 파일1 파일2 …)

처음에는 라이브러리 이름이 쓰이고 두번째는 라이브러리의 종류를 나타냅니다. Default STATIC 쓰이며 아래와 같이 3개의 종류 하나를 지정할 있습니다.

STATIC, SHARED, MODULE

그리고 후에는 사용될 파일을 나열하면 됩니다.

 

위의 CMakeLists.txt 수정하여 소스파일 a 공유 라이브러리를 만든다면 아래와 같이 수정하시면 됩니다.

 

SET(SRC_FILES a.c)

SET(LIB_NAME a)

ADD_LIBRARY(${LIB_NAME} SHARED ${SRC_FILES})

 

후에 빌드하면 lib(라이브러리 이름).so 파일이 생기는 것을 확인할 있습니다

ADD_DEFINITIONS()

전처리시에 정의할 매크로를 의미합니다. 앞에 -D 쓰고 나서 매크로명을 써야하며 #define 구문의 역할을 한다고 보시면 됩니다. 2가지 사용법이 있습니다. 컴파일시에 -D매크로명 옵션과 같습니다.

ADD_DEFINITIONS(-D매크로명) : 단순 define으로 매크로를 정의할때 사용합니다.

ADD_DEFINITIONS(-D매크로명=) : 매크로에 값을 집어넣습니다.

 

INCLUDE_DIRECTORIES()

#include에서 사용할 디렉토리를 명시합니다. 만약 inc 디렉토리에 헤더파일이 존재하는 경우 아래와 같이 사용하면 됩니다.

INCLUDE_DIRECTORIES(inc)

 

이상 간단하게 CMake의 개념과 사용법을 알아보았습니다. 다음에는 더 심도있게 알아보도록 하겠습니다.

 

 

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

북센티넬 섬(North Sentinel Island)

인도양 동부 벵골만 열도에 있는 안다만 섬 중 하나로 북 센티넬 섬인데요.

겉으로 봐서는 무인도 같지만 사람이 살고 있는 섬입니다. 사람이 살고 있기는 한데, 절대 친절하지 않은 사람들이 살고 있어요. 그러니까 아무것도 모르고 들어갔다가는 이미 거기 살고 있는 형님들한테 화살맞고 죽을 수 있죠.

이곳에 살고 있는 센티넬 족이라 불리는 원주민들이 살고 있습니다.

 

셀티넬 족은 6만 년이 넘도록 외부와 철저히 단절되어 있습니다. 그리고 외부인을 보면 먼저 죽이려고 듭니다. 그러니 뭣 모르고 섬에 들어갔다가는 ㅈ될수 있다는 겁니다. 그리고 앞으로 해외여행 리스트에서 북센티넬 섬은 지우도록 하세요.

 

 

적대적인 이유

센티넬 족은 왜 이렇게 우리에게 적대적일까요? 처음부터 그랬던 걸까요?

원래 먼저 선빵쳤던 건 외부인이었습니다. 원래 맞은 사람은 상처로 남고 때린 사람은 기억 못하잖아요? 저도 저 때렸던 사람 다 기억하거든요

예로부터 외부로부터 약탈을 당해왔습니다. 그리고 인도가 영국의 식민지배를 당할 때 영국군의 무력 시위도 있었습니다. 특히 결정적인 것은 외부 한 탐험대가 노인 한쌍과 아이 4명, 총 6명을 납치하였습니다. 노인 2명은 면역력이 떨어져 사망했고 아이 4명은 돌려보냈다고 하네요.

그런 사건들이 있는데 외부인을 환영할 리가 없죠. 저같아도 제가 안죽으려면 활들고 먼저 쏩니다.

 

공격 사례

1974년 센티넬족을 다룬 다큐멘터리도 네셔널 지오그래픽에서 방영했었는데 센티넬 족들은 환영할리 없죠. 괜히 들어갔다가 부상을 입게 됩니다. 그래도 부상만으로 끝나서 다행인 정도인거에요. 

괜히 불법으로 고기잡다가 술 먹고 배가 좌초되어서 죽은 사람들도 있으니까요. 실제 좌초된 배는 구글어스에 찍혀있습니다.

구글어스에 North Sentinel Island를 검색해서 그 배를 찾아보세요. 저는 찾았습니다.

 

접근 포기

2004년 남아시아 대지진이 발생했을 때 이 섬도 역시 큰 피해를 입었을 것이라는 추측으로 헬기를 보냈는데요. 물론 구호물자도 실어서 헬기를 보냈는데요. 묻지도 따지지도 않고 한결같이 그들은 화살로 대응하였고 삔또 상한 정부는 그냥 알아서 복구하라고 냅뒀습니다.

마음의 상처가 컸던 탓일까요? 2005년 센티넬족과는 손절할 것이라는 발표를 하고 주위에 들어갈 수 없게 만들어버립니다. 

 

사실 현대식 무기로 충분히 제압은 가능하지만 국제적으로 용납될 수 없고 인도적이지 않습니다. 그래도 나라가 인도인데

 

그밖에...

미 선교사 살해사건도 유명한 사건인데요. 괜히 종교 전파하러 갔다가 싸늘한 주검이 됩니다. 그들이 항상 외부인에게 공격적이지 만은 않았습니다. 인도의 조사단이 처음으로 그들과 교류하게 됩니다.

보다 자세한 내용은 아래 영상을 시청해주세요.

 

센티넬 섬 영상

 

 

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

동기화, 임계 영역 등 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

교착상태(Deadlock)

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가 점유한 자원을 의미하고 각 자원은 소유된 프로세스쪽으로 화살표가 나가고 있습니다. 프로세스들의 화살표는 요구하는 자원쪽으로 향하고 있지요. 여기서 이 화살표들의 방향대로 나가면 사이클을 그리며 무한정 순환하게 됩니다.

 

 

 

 

 

교착상태 발생 코드

아래의 코드는 교착상태를 스레드를 이용해서 발생한 예제입니다. 여기서는 간단히 메인 스레드와 메인 스레드에서 생성된 스레드를 고려합니다.

여기서 자원은 뮤텍스인 lock1과 lock2를 말하지요. 

 

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>


pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void *function(void* arg){

        sleep(1);       //main thread가 먼저 실행할 여지를 줌
        printf("\t thread start\n");

        pthread_mutex_lock(&lock2);
        pthread_mutex_lock(&lock1);

        printf("\t critical section \n");

        pthread_mutex_unlock(&lock1);
        pthread_mutex_unlock(&lock2);

        printf("\t thread end\n");

}

int main(){
        pthread_t tid;

        printf("main thread start and create thread\n");
        pthread_create(&tid,NULL,function,NULL);

        pthread_mutex_lock(&lock1);
        sleep(5);               //thread가 lock2를 먼저 실행할 여유를 줌
        pthread_mutex_lock(&lock2);

        printf("critical section \n");

        pthread_mutex_unlock(&lock2);
        pthread_mutex_unlock(&lock1);

        pthread_join(tid,NULL);
        printf("main thread end\n");

}

 

(정확히 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) 교착 상태 회피

예방처럼 교착 상태의 발생 가능성을 미리 제거하지 않고 교착상태가 일어난다고 보고 이것을 회피하는 것입니다. 예를 들어 몇 초동안 프로세스나 스레드가 임계 구역 전에 대기 중이라면 이 시간이 지난 후 다음 작업을 수행합니다. 위의 예제코드를 아래와 같이 수정할 수 있습니다.

 

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>


pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;

void *function(void* arg){
        struct timespec tout;
        struct tm* t;
        int locked;

        sleep(1);       //main thread가 먼저 실행할 여지를 줌
        printf("\t thread start\n");


        clock_gettime(CLOCK_REALTIME,&tout);
        tout.tv_sec += 5;

        pthread_mutex_lock(&lock2);
        locked = pthread_mutex_timedlock(&lock1,&tout);  //5초간만 lock이 걸림

        printf("\t critical section \n");

        if(locked == 0){
                pthread_mutex_unlock(&lock1);
        }
        pthread_mutex_unlock(&lock2);

        printf("\t thread end\n");

}

int main(){
        pthread_t tid;

        printf("main thread start and create thread\n");
        pthread_create(&tid,NULL,function,NULL);

        pthread_mutex_lock(&lock1);
        sleep(5);               //thread가 lock2를 먼저 실행할 여유를 줌
        pthread_mutex_lock(&lock2);

        printf("critical section \n");

        pthread_mutex_unlock(&lock2);
        pthread_mutex_unlock(&lock1);

        pthread_join(tid,NULL);
        printf("main thread end\n");

}

 

pthread_mutex_timed_lock 함수는 특정 시간동안 lock을 얻을 수 없다면 뮤텍스를 잠그지 않고 ETIMEDOUT이라는 오류 부호를 돌려줍니다. 따라서 스레드가 무한정 차단되지 않게 만듭니다. 이 코드를 실행시켜보면 아래와 같이 끝나기는 합니다.

 


# ./a.out
main thread start and create thread
         thread start
         critical section
critical section
         thread end
main thread end

 

 

3) 교착 상태 회복 

교착 상태를 시스템에서 탐지하여 회복시키는 알고리즘으로 교착상태를 회복합니다.

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

 

아래와 같이 caculator라는 프로그램이 같은 디렉토리에 있는 lib내의 libcalcops라는 라이브러리를 사용할 수 있도록 만들어보도록 하겠습니다.

 

아주 간략하게 무슨 프로그램이냐를 설명해드리면, calculator라는 이 프로그램은 사용자로부터 두 수를 입력받아 덧셈, 뺄셈, 나눗셈의 결과를 출력해주는 프로그램입니다.

우리는 더 쉽게 calculator라는 프로그램을 구현하기 위해서(사실은 그냥 짜는게 더 쉽지만 라이브러리를 연동하는 방법을 알기 위해서) lib/libcalcops의 라이브러리를 가져다가 쓸겁니다. 물론 우리가 만들어 쓸거에요.

이 라이브러리에는 실제 연산 동작(calc operation)이 구현되어있습니다.

 

 

우선 현재 디렉토리에 mkdir lib를 이용해서 lib를 저장할 수 있는 디렉토리를 만들어줍시다.

 


# mkdir lib
# cd lib

 

라이브러리 소스코드 작성

너무나 간단한 연산 동작을 하는 함수를 정의한 헤더파일과 구현한 c파일을 작성하고 컴파일 해줍니다.

 

calc_operations.h

#ifndef __CALC_OPERATION_H_
#define __CALC_OPERATION_H_
int add(int a,int b);
int sub(int a,int b);
int mult(int a,int b);
double div(int a,int b);
#endif

 

calc_operations.c

#include "calc_operations.h"

int add(int a,int b){
    return a+b;
}

int sub(int a,int b){
    return a-b;
}

int mult(int a,int b){
    return a*b;
}

double div(int a,int b){
    return (double)a/(double)b;
}

 

여기까지는 뭐 누구나 아는 프로그램이지요. 아래와 같이 컴파일하게 되면 object파일이 하나 생성되겠죠.


# gcc -c calc_operations.c
# ls
calc_operations.c  calc_operations.h  calc_operations.o 

 

이제 이것을 ar로 rc옵션을 주어 정적라이브러리 아카이브를 만듭니다. 


# ar rc libcalcops.a calc_operations.o
# ls
calc_operations.c  calc_operations.h  calc_operations.o  libcalcops.a

 

프로그램 구현

이제 lib밖으로(cd ..) 이동해주세요. 여기서 소스 코드를 구현하도록 하겠습니다.

 

calculator.c

#include <stdio.h>
#include "lib/calc_operations.h"

int main(){
        int a,b;
        scanf("%d %d",&a,&b);

        printf("%d + %d = %d\n", a,b,add(a,b));
        printf("%d - %d = %d\n", a,b,sub(a,b));
        printf("%d * %d = %d\n", a,b,mult(a,b));
        printf("%d / %d = %f\n", a,b,div(a,b));

}

 

이제 아래와 같이 컴파일하도록 해봅시다. 아래 옵션 중에서 -L은 라이브러리가 위치한 디렉토리를 명시해주고 -lcalcops는 우리가 작성한 lib파일 이름입니다. 우리가 lib파일 이름을 libcalcops.a라고 정의하였을때 옵션은 -lcalcops라고 명시해주면 됩니다. lib가 -l로 바뀌고 확장자 .a를 빼주는 식이죠.


# gcc calculator.c -L ./lib -lcalcops
# ./a.out
7 3
7 + 3 = 10
7 - 3 = 4
7 * 3 = 21
7 / 3 = 2.333333

기존의 .o파일과 .a를 지우고 나서도 calculator 프로그램이 동작할까요? 지우고 실행을 다시 해보도록 합시다.


# rm -rf *.a *.o
# ls
calc_operations.c  calc_operations.h
# cd ..
# # ./calculator
10 3
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3.333333

보시다시피 잘 동작하는 것을 알 수 있습니다. 추측해볼 수 있는 것은 런타임과는 관계가 없고 컴파일시(calculator)에 프로그램에 적재되는 것을 알 수 있고 실제로도 그렇습니다.

 

 

 

공유라이브러리

공유라이브러리는 컴파일 방식만 변경하면 됩니다. lib 디렉토리로 이동해주세요. 

아래의 명령으로 라이브러리를 컴파일해줍니다. 그런후에 /usr/lib 디렉토리에 복사해줍니다.


 # gcc -shared -o libcalcops.so calc_operations.c
 # cp libcalcops.so /usr/lib

 

이제 calculator 프로그램을 컴파일 할것인데, 위의 컴파일 옵션보다 더 간단합니다.


# gcc calculator.c -lcalcops
# ./a.out
8 3
8 + 3 = 11
8 - 3 = 5
8 * 3 = 24
8 / 3 = 2.666667

 

그렇다면 아까와 같이 /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 명령을 주어 반영하면 됩니다.

libc.conf x86_64-linux-gnu.conf
# libc default configuration
/usr/local/lib
/usr/lib
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

데몬, systemd에 관한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

리눅스 데몬(Daemon)

백그라운드에서 실행되는 일종의 프로세스이며 오랫동안 유지되는 프로세스입니다. 주로 시스템이 시작될때 같이 시작이 되서 시스템이 종료될때 같이 종료되는 경우가 대부분입니다. 백그라운드에서 돌면서 사용자가 원하는 서비스를 제공하는 프로세스를 의미합니다. 데몬은 일반 백그라운드 프로세스와는 다르게 제어 터미널이 없고 표준 입출력이 없습니다. 즉, 사용자로부터 직접적인 입력를 받지 않고 출력도 하지 않습니다.

데몬의 예는 뭐가 있을까요? 관례상 데몬은 프로세스 이름이 d로 끝납니다. 예를 들어 네트워크 인터페이스들을 감시하고 서버로부터 요청을 감지하는 inetd, 설정된 날짜와 시간이 되면 지정된 명령을 실행시켜주는 cron, 보안 원격 로그인을 제공하는 sshd와 같은 것들이 있지요. 이러한 데몬을 생각해보세요. 사용자가 입력하지도, 출력을 내보내지도 않습니다.

ps명령어를 통해서 어떤 데몬이 있는지 보도록 해봅시다.

   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      0       1       1       1 ?             -1 Ss       0   1:42 /sbin/init splash
      0       2       0       0 ?             -1 S        0   0:00 [kthreadd]
      2       3       0       0 ?             -1 I<       0   0:00 [rcu_gp]
      2       4       0       0 ?             -1 I<       0   0:00 [rcu_par_gp]
      2       6       0       0 ?             -1 I<       0   0:00 [kworker/0:0H-kblockd]
      2       9       0       0 ?             -1 I<       0   0:00 [mm_percpu_wq]
      2      10       0       0 ?             -1 S        0   0:17 [ksoftirqd/0]
      2      11       0       0 ?             -1 I        0   0:51 [rcu_sched]
      2      12       0       0 ?             -1 S        0   0:01 [migration/0]
      2      13       0       0 ?             -1 S        0   0:00 [idle_inject/0]
      2      14       0       0 ?             -1 S        0   0:00 [cpuhp/0]
      2      15       0       0 ?             -1 S        0   0:00 [kdevtmpfs]
      2      16       0       0 ?             -1 I<       0   0:00 [netns]
      2      17       0       0 ?             -1 S        0   0:00 [rcu_tasks_kthre]
      2      18       0       0 ?             -1 S        0   0:00 [kauditd]
      2      19       0       0 ?             -1 S        0   0:00 [khungtaskd]
      2      20       0       0 ?             -1 S        0   0:00 [oom_reaper]
      2      21       0       0 ?             -1 I<       0   0:00 [writeback]
      2      22       0       0 ?             -1 S        0   0:00 [kcompactd0]
      2      23       0       0 ?             -1 SN       0   0:00 [ksmd]
      2      24       0       0 ?             -1 SN       0   0:00 [khugepaged]
      2      70       0       0 ?             -1 I<       0   0:00 [kintegrityd]
      2      71       0       0 ?             -1 I<       0   0:00 [kblockd]
      2      72       0       0 ?             -1 I<       0   0:00 [blkcg_punt_bio]
      2      73       0       0 ?             -1 I<       0   0:00 [tpm_dev_wq]
      2      74       0       0 ?             -1 I<       0   0:00 [ata_sff]
      2      75       0       0 ?             -1 I<       0   0:00 [md]
      2      76       0       0 ?             -1 I<       0   0:00 [edac-poller]
      2      77       0       0 ?             -1 I<       0   0:00 [devfreq_wq]
      2      78       0       0 ?             -1 S        0   0:00 [watchdogd]
      2      83       0       0 ?             -1 S        0   0:01 [kswapd0]
      2      84       0       0 ?             -1 S        0   0:00 [ecryptfs-kthrea]
      2      86       0       0 ?             -1 I<       0   0:00 [kthrotld]
      2      87       0       0 ?             -1 I<       0   0:00 [acpi_thermal_pm]
      2      88       0       0 ?             -1 S        0   0:00 [scsi_eh_0]
      2      89       0       0 ?             -1 I<       0   0:00 [scsi_tmf_0]
      2      90       0       0 ?             -1 S        0   0:00 [scsi_eh_1]
      2      91       0       0 ?             -1 I<       0   0:00 [scsi_tmf_1]
      2      93       0       0 ?             -1 I<       0   0:00 [vfio-irqfd-clea]
      2      95       0       0 ?             -1 I<       0   0:00 [ipv6_addrconf]
      2     104       0       0 ?             -1 I<       0   0:00 [kstrp]
      2     107       0       0 ?             -1 I<       0   0:00 [kworker/u3:0]
      2     120       0       0 ?             -1 I<       0   0:00 [charger_manager]
      2     121       0       0 ?             -1 I<       0   0:37 [kworker/0:1H-kblockd]
      2     166       0       0 ?             -1 S        0   0:00 [scsi_eh_2]
      2     167       0       0 ?             -1 I<       0   0:00 [scsi_tmf_2]
      2     187       0       0 ?             -1 S        0   0:15 [jbd2/sda5-8]
      2     188       0       0 ?             -1 I<       0   0:00 [ext4-rsv-conver]
      1     227     227     227 ?             -1 S<s      0   0:11 /lib/systemd/systemd-journald
      1     252     252     252 ?             -1 Ss       0   0:26 /lib/systemd/systemd-udevd

 

데몬 코딩 룰

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로 지정합니다. 데몬은 사용자로부터 입력을 받을 필요가 없고, 출력, 에러를 직접 하지 않습니다. 데몬에는 제어 터미널 장치가 연관되어 있지 않고 출력이 표시될 대상이나 사용자로부터 입력을 받을 통로가 없습니다.

데몬 코딩

위의 코딩 룰 대로 코딩한 데몬 프로그램입니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

void daemonize(const char *cmd){
        int i,fd0,fd1,fd2;
        pid_t pid;
        struct rlimit rl;
        struct sigaction sa;

        //umask를 0으로
        umask(0);

        if(getrlimit(RLIMIT_NOFILE,&rl)<0){
                printf("%s: can't get file limit\n",cmd);
                exit(0);
        }

        //자식 프로세스 생성 후 부모는 종료
        if((pid=fork())<0){
                printf("%s: can't fork\n",cmd);
                exit(0);
        }else if(pid !=0)
                exit(0);

        //세션을 만들고 리더가 됨.
        setsid();

        //SIGHUP 무시, 제어터미널 연결이 끊겨도 무시
        sa.sa_handler=SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        if(sigaction(SIGHUP,&sa,NULL)<0){
                printf("%s: can't ignore SIGHUP\n",cmd);
                exit(0);
        }
        if((pid=fork())<0){
                printf("%s:can't fork\n",cmd);
                exit(0);
        }
        else if(pid!=0)
                exit(0);


        // 작업 디렉토리를 /로 변경
        if(chdir("/") < 0){
                printf("%s: can't change directory to /\n",cmd);
                exit(0);
        }

        //열려있는 파일 디스크립터 닫기
        if(rl.rlim_max == RLIM_INFINITY)
                rl.rlim_max = 1024;
        for(i=0;i<rl.rlim_max;i++)
                        close(i);

        //모두 닫았으니 fd는 0부터 return되며 모두 /dev/null로 직행
        fd0=open("/dev/null",O_RDWR);
        fd1=dup(0);
        fd2=dup(0);

        openlog(cmd,LOG_CONS,LOG_DAEMON);
        if(fd0 != 0 || fd1 != 1 || fd2 != 2){
                syslog(LOG_LOCAL0," unexpected fd : %d %d %d",fd0,fd1,fd2);
                exit(1);
        }

        syslog(LOG_LOCAL0,"helloworld! [%s]start",cmd);
}


int main(int argc,char* argv[]){
        daemonize(argv[0]);

        //100초후 데몬 종료
        sleep(100);
}

 

이후에 compile하고 실행시켜봅시다. 


# gcc daemon.c -o dd
# ./dd

아무 변화가 없죠. 이제 ps -axj로 실행시켜보세요.


# ps -efj
...
      1  105975  105974  105974 ?             -1 S        0   0:01 ./dd
 104369  105977  105977  103273 pts/2     105977 R+       0   0:00 ps -axj
...

 

./dd라는 프로세스가 보이네요. 만약 정상적인 프로세스라면 부모 pid도 같이 보여야합니다. 찾아볼까요?


# ps -axj | grep 105974 
      1  105980  105974  105974 ?             -1 S        0   0:01 ./dd

없음을 알 수 있네요. 부모프로세스는 105974인데 먼저 종료가 되었죠. 그렇기 때문에 initd가 그 프로세스를 짬처리 당했고 자식 프로세스는 setsid로 새로운 세션의 리더가 됩니다. 

이러한 사실들을 보면 daemon이 올바르게 초기화되었다는 것을 알 수 있습니다.

 

로그 기록

데몬은 입출력 통로가 없기 때문에 사용자에게 특정한 사실을 알리기 위해서는 파일에 기록하는 식으로 알리게 됩니다.  이때 만약 데몬마다 로그파일이 다르다면 시스템 관리자 입장에서는 어떤 로그파일이 무슨 데몬의 로그인지 확인하기가 까다롭게 됩니다.

그래서 로그를 중앙 집중적으로 관리하게 되고 이때 이 로그를 관장하는 데몬이 syslogd입니다. syslogd를 사용하기 위해서는 우선 syslog가 설치가 되어있어야겠죠. 

이후에는 /etc/syslog.conf 파일 맨 아래에 다음과 같은 설정을 합니다. 

local0.*        /var/log/dd_info

이제 변경사항을 반영하기 위해서 syslogd를 재시작합니다.

# /etc/init.d/syslog restart

 

이제 우리의 데몬을 실행시켜보도록 합시다. 이제 /var/log/dd_info에 우리의 로그가 나오는것을 확인할 수 있을 것입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

select, poll, epoll과 같이 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

다중입출력 

아래와 같은 상황을 생각해볼까요? 어떤 프로세스가 다음과 같이 파일 3개를 처리하는데 그 중 입력이 있는 파일을 프로세스에 처리하는 상황을 어떻게 구현할 수 있을까요?

이때 우리는 쓰레드를 생각해볼 수 있겠지요. 나쁘지 않은 아이디어입니다. 그렇다면 파일 3개의 대해서 3개의 쓰레드를 돌려야하겠네요.

여러분도 알다시피 쓰레드는 많이듭니다. context switching이 대표적이죠. 그리고 파일 처리에 대해서 동기화 기법을 적용해야할 수도 있습니다. 상당히 복잡하고 어려운 구현이 될 수도 있습니다.

이제 우리는 스레드를 이용하는 방법말고 보다 경량적인 방법을 선택할 것인데 이러한 구현을 도와주는 system call이 바로 select, poll, epoll 입니다. 이 포스팅에서는 select에 대해서만 설명합니다.

1. select

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct tiemval *timeout)

- 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개의 매크로를 지원합니다.

 

매크로 설명
void FD_CLR(int fd, fd_set *set) set에서 fd를 제거합니다. 더 이상 검사하지 않습니다.
int  FD_ISSET(int fd, fd_set *set) set에 fd의 플래그가 setting되었는지 확인합니다. 
void FD_SET(int fd, fd_set *set) set에 fd를 추가합니다. 이것은 flag를 setting하는 것이 아닌 검사를 하겠다는 뜻의 setting입니다.
void FD_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로 아무 로그나 찍으면 엄청나게 루프를 도는 것을 확인할 수 있습니다. 여기서는 이해를 돕기 위해서 만든 프로그램일 뿐입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

발단

우선 기억은 나지 않으나 고등학교 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기 사진은 왜 찾는건지는 모르겠다. 개발때려치고 치질 전문 블로그로 바꿔야되나.

 

아무쪼록 수술 잘 받아 완쾌하시어 쾌변의 즐거움을 다시 만끽하세요.  

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,