DNS(Domain Name System)

우리들이 www.naver.com 이나 www.google.com 를 웹브라우저를 통해 접속할때 문자열로 된 주소를 타이핑하여서 웹 사이트에 접속을 하게 되죠? 근데 컴퓨터는 글자로 된 주소는 사실 모르고, 서버 컴퓨터의 IP를 이용해서 웹사이트에 접속하게 됩니다. naver에 접속을 하면 위의 www로 시작하는 주소를 입력하여 접속해도 되지만 이번에는 IP주소를 직접 쳐서 들어가보세요. IP를 알아볼 수 있는 명령어는 nslookup입니다. 저는 naver와 nate의 주소를 알아보았습니다.

nslookup - naver
nslookup - nate

223.130.195.200이라는 IP주소를 웹브라우저에 쳐서 들어가도 똑같이 네이버에 접속이 가능합니다. 네이트의 주소는 120.50.131.112네요.

whois 명령을 사용하면 도메인의 등록 정보를 알아볼 수 있습니다. 단, 업체에서 whois서버가 따로 존재해야합니다. 사용 방법은 whois 도메인명 혹인 whois IP주소 를 쳐서 도메인 등록 정보를 알 수 있습니다. google도메인에 대한 정보를 알아봤습니다. whois google을 타이핑해보세요.

whois google

 

아무튼 그렇다면 컴퓨터가 어떻게 이 주소를 알수 있을까요? 컴퓨터는 다음의 순서로 알아냅니다. 

1. 로컬 DNS Cache에서 IP 검색

2. hosts파일에서 IP 검색

3. DNS 서버에 질의

로컬에 저장된 캐시에 해당 IP주소가 있는지 확인하고 그래도 없으면 hosts파일에서 IP를 찾아옵니다. (참고로 DNS Cache를 삭제하려면 /etc/init.d/dns-clean restart 명령을 사용하여 삭제하면 됩니다.)
이 DNS 캐시에도 없으면 DNS서버에 물어보게 되죠. hosts파일은 무엇일까요? 리눅스에서 /etc/hosts파일을 아래와 같이 수정해봅시다. 네이버의 주소를 네이트의 IP주소를, 네이트의 주소를 네이트의 IP주소로 저장해 놓으면 www.naver.com  접속시 네이트가 접속이 됩니다. 물론 악의적인 사람은 이 파일을 악용하는 사람들도 있겠죠?

 

 

hosts파일에도 맞는 IP주소가 없으면 이제 서버에 여쭈어보게 됩니다. 이 서버 불쌍한 놈입니다.. 우리가 알려달라고 하면 여기 저기 물어보고 그제서야 알려줍니다.

dns 질의

사용자의 컴퓨터는 google의 주소를 서버에 물어봅니다. 우리가 www.google.com이라고 치면 사실 www.google.com. 으로 되어 접속이 됩니다. 여기 맨 끝에 끝나는 . 을 루트 도메인이라고 부릅니다. 자, 우리가 직접 물어본 서버는 이제 최상위의 Root DNS서버(.)에게 google IP에 대해 질의합니다. Root DNS 서버는 다음 도메인(.com)의 서버를 알려줍니다. 이제 우리 서버는 COM 서버에 질의합니다. 이 불친절한 com 서버도 역시 google.com의 서버에게 질의하라고 넘깁니다. 이제 google.com 서버에 질의합니다. google.com은 www.google.com의 IP주소를 를 알려줍니다. 이제 우리의 서버는 IP주소를 사용자에게 알려주지요. 이렇게 주소를 받은 사용자 컴퓨터는 다음에 또 물어보기 미안하기 때문에 DNS Cache에 일정시간 동안 저장합니다.

이때 사용자가 질의하는 방식을 재귀적 질의(Recursive Query)라고 하며, DNS서버가 다른 DNS서버에게 질의하는 방식을 반복적 질의(Iterative Query)라고 합니다. 우리가 Recursieve Query를 날리는 서버는 보통 인터넷 서비스 제공 업체의 서버 주소입니다. Root DNS 서버나 Com DNS 서버를 Authoritative DNS 서버라고 합니다.

DNS서버의 IP주소는 어디에 기록되어 있을까요? 리눅스에서 /etc/resolv.conf 가 DNS 서버의 주소를 갖고 있습니다. 

/etc/resolv.conf

 

DNS는 보통 UDP 53번 포트를 사용합니다. TCP 53번도 쓰는 경우가 있는데, 이것은 DNS 헤더의 특수한 flag를 설정하면 TCP로 DNS 연결을 할 수 있고, 응답하는 데이터가 512 바이트를 넘어갈 경우 TCP를 통해 응답합니다.

실제 DNS 헤더는 어떤 정보를 담고 있을까요? 다음은 저의 DNS 패킷 중 하나입니다. 제가 사용하는 DNS서버의 주소는 210.220.163.82 인것을 알 수 있습니다. 여기에 Transaction ID와 Flag, 그리고 가장 밑에 질의(Query)를 볼 수 있습니다. 질의 형태(type)은 주로 A인데요. DNS의 본질인 IP주소를 받아오는 질의 타입이 A입니다.

 

 

DNS Query 요청

그리고 그에 대한 응답입니다. 응답에서 Transaction ID를 보면 이전에 요청했던 Transaction ID와 같은 것을 볼 수 있습니다. 그리고 우리가 알고 싶은 IP주소는 Answer필드에 있는 것을 볼 수 있네요.

DNS response

 

DNS의 패킷 그대로를 살펴보면 가장 마지막 4바이트가 우리가 원하는 주소라는 것을 알 수 있습니다. AC D9 A1 EE(172 217 161 238)이 주소이지요.

DNS raw packet

 

DNS의 질의는 아래 표에 정리되고 있습니다.

질의 TYPE 설명
A 도메인에 대한 IP 정보를 담고 있습니다.
ANY 도메인에 대한 모든 정보를 질의합니다.
MX 메일 서버에 대해 질의합니다.
NS Name Server에 대한 질의를 합니다.
SOA zone파일의 SOA 에 대해 질의합니다.
HINFO Host에 대한 정보의 질의를 합니다.
PTR Reverse Zone 파일에 설정된 PTR 레코드를 질의합니다. 
CNAME 호스트의 Alias 정보에 대한 질의입니다.

 

이상으로 DNS에 대한 개념과 실 데이터를 구경해보았습니다. 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

ARP(Address Resolution Protocol)

ARP는 논리적인 주소(Logical Address)를 물리적 주소(Physical Address)로 변환하는 프로토콜을 의미합니다. 논리적인 주소라함은 IP주소, 물리적 주소는 MAC 주소(하드웨어 주소)라고 생각하면 훨씬 편합니다. IP주소는 어떻게 생겼죠?

예를 들어 저의 IP는 192.168.35.10이라고 해봅시다. 그리고 저희 MAC주소는 AA:BB:CC:DD:EE:FF라고 해볼게요. 저의 IP주소는 저 말고도 다른 사람도 쓸 수 있습니다. 왜냐면 네트워크는 특정 구역마다 나누어져 있으니까요. 쉽게 라우터를 기준으로 나누어져 있다고 생각하시면 됩니다. 그래서 논리적인 주소라 칭합니다. 저의 MAC주소는 이 세상에 단 하나밖에 없습니다. 하드웨어 고유한 주소이기 때문에 물리적 주소라고 합니다.

같은 네트워크에서는 IP주소로 통신하지 않고 물리적 주소를 가지고 통신합니다. 즉, MAC주소를 사용하여 통신한다는 것이죠. 스위치가 왜 2계층에 동작하는 것이 그 이유입니다. 2계층에서 MAC주소만 보아 정보 전송을 하면 되니까요.

어쨌든, IP주소를 MAC주소로 변환하는 프로토콜이 ARP라고 하는 거죠. ARP의 동작 방식을 보도록 합시다. 

1. 초기 Host A는 192.168.30.6에 MAC주소를 모르니까 네트워크 전체에 192.168.30.6에 대한 MAC주소를 물어보는 요청을 보냅니다.

ARP Request

 

2. 이 IP주소에 맞는 다른 호스트는 자신의 MAC주소를 담아 응답을 보냅니다. 요청 메시지에 자신의 MAC주소를 요청한 호스트 A의 IP주소가 있기 때문에 A에게 응답을 곧장 보낼 수 있습니다. 

ARP Reply

 

3. MAC주소를 알아낸 호스트 A는 ARP의 정보를 담고 있는 저장소에 192.168.30.6의 MAC주소를 업데이트 합니다. 이 저장소를 ARP Cache라고 하며 일정 시간 후에 이 정보를 삭제합니다.

 

RARP(Reverse ARP)

반대로 MAC주소를 IP주소로 변환하는 프로토콜은 무엇일까요? ARP의 반대 과정을 거친다고 하여 RARP(Reverse ARP)라고 합니다. RARP 동작이 되려면 따로 RARP 서버가 존재하여야합니다. 자신의 IP를 모를때 이 프로토콜을 사용합니다. 동작방식은 아래의 순서와 같습니다.

1. 최초 호스트 A는 자신의 IP를 모르는 상태이기 때문에 IP를 요청하는 RARP Request를 Broadcasting으로 보냅니다. 이때 요청에는 자신의 MAC주소가 기재되어있습니다.

RARP Request

 

 

2. RARP 서버는 요청한 호스트 A의 IP 주소를 담은 RARP 응답 메시지를 만들어 Host A에게 전송합니다. 

RARP Reply

 

ARP Cache

실제 ARP cache는 어떻게 생겼을까요?  cmd 창을 열고 arp -a 명령을 처보면 아래와 같이 ARP Cache 내용을 볼 수 있습니다. 저는 가장 호스트까지 있기 때문에 인터페이스가 2개가 잡히는데, 아니면 하나만 보일 수 있습니다. 여기에 IP주소와 MAC주소가 보이시죠? 그리고 맨 오른쪽에 정적, 동적이 보이는데, 정적은 등록된 ARP정보가 수동으로 시스템 종료가 되기 전까지 영구적으로 설정이 된것이고 동적은 컴퓨터가 알아서 설정한 것이고 시간이 지나면 없어지게 됩니다.

arp -a

ARP Cache내용을 지우려면 arp -d를 사용하시면 됩니다. arp -d를 쳤을때 관리자 권한이 필요하다고 나온다면 cmd를 오른쪽 마우스를 클릭하면 관리자 권한으로 실행이라는 항목이 보이실 겁니다. 이것을 통해 cmd를 실행해보세요.

지워도 되냐구요? 단순히 컴퓨터를 쓰는 일반 사용자라면 지우셔도 됩니다. 다시 업데이트 될거니까요. 그 후 arp -a를 보면 반드시 필요한 몇개의 MAC주소를 제외하면 지워진것을 볼 수 있습니다. 

그리고 정적으로 MAC주소를 등록하려면 arp -s를 이용하면 됩니다. 아래와 같이 "arp -s IP주소 MAC주소" 를 입력하면 됩니다.

arp -s

 

 

ARP 스푸핑(ARP Spoofing)

혹은 ARP Cache Poisoning이라고 합니다. 공격자는 두 호스트의 통신을 감시하기 위해서 두 호스트의 ARP Cache를 변경하는 것을 공격방법입니다. 공격자는 아래의 그림처럼 지속적으로 ARP 응답을 보냅니다. 물론 잘못된 정보로 말이죠. 

 

 

만약 동적으로 ARP 정보가 등록되어있다면 시간이 지나면 삭제되는 것을 이용합니다. 그때 응답을 받으면 피해 컴퓨터는 잘못된 정보로 ARP Cache를 업데이트하게 됩니다. 여기서 AA:AA:AA:AA:AA:AA의 MAC주소를 갖는 호스트를 Alice, BB:BB:BB:BB:BB:BB의 MAC주소를 갖는 호스트를 Bob라고 부르도록 하겠습니다. 공격자 자신은 정상적인 ARP Cache를 가지고 있습니다.

이때 Alice가 Bob에게 데이터를 전송할때 C에게 그 정보를 전송합니다. 맨 처음에 같은 네트워크는 MAC주소만 보고 그 데이터를 전달한다고 했죠? 그러니 공격자의 MAC주소인 CC:CC:CC:CC:CC:CC으로 전달하게 되죠. 공격자는 이제 Alice의 데이터를 볼 수 있죠. 이것을 다시 정상적인 Bob에게 전달합니다. 이렇게 Alice와 Bob를 속이고 그 당사자인척 하는 공격방법이 스푸핑이라고 합니다.

ARP spoofing

 

반대로 Bob이 Alice에게 데이터를 보낼때도 공격자를 거쳐가게 되죠. Bob의 ARP Cache도 오염되어 공격자의 MAC주소로 업데이트되었기 떄문이죠.

공격자는 2계층의 MAC주소를 그 사람인척 위장해야하므로 같은 네트워크에 위치해야합니다. 

대응

- 위에서 본 정적 arp cache를 구성하면 시스템 종료시까지 막을 수 있습니다. 또한 시스템 가동시마다 arp를 정적으로 구성해주어야합니다. 물론 매번 arp를 정적으로 구성하는 것은 까다로울 수 있으니 자동화된 프로그램을 사용하는 것이 좋겠죠.

- arp cache의 변경을 감지하는 프로그램을 사용할 수도 있습니다.

여기까지 ARP에 대한 개념과 설명, 그리고 ARP Cache와 Reply를 통해서 스푸핑할 수 있는 원리에 대해서 알아보았습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

디피-헬만 (Diffie-Hellman) 

디피-헬만(이하 DH)은 대칭키를 키 분배 센터(KDC, Key Distribution Center)없이 대칭키를 생성할 수 있는 암호 프로토콜을 의미합니다. KDC가 뭐냐하면 키를 분배하고 관리해주는 센터를 의미하는데요. 수 많은 호스트가 있는데 암호화 통신을 하려면 그 호스트들의 대칭키를 모두 가지고 있어야하니, KDC에게 키를 얻습니다. KDC는 물론 신뢰성이 큰 센터여야겠지요.  DH는 KDC없이도 대칭키를 서로 합의할 수 있도록 만들어줍니다. 아래의 그 과정을 봅시다.

우선 Alice와 Bob(암호학에서는 Alice와 Bob 이름에 친숙해지셔야합니다.)이 먼저 거쳐야 할 과정이 있습니다.

1) Alice와 Bob은 p와 g를 정하는데요. 매우 큰 300자리가 넘는 매우 큰 소수 p를 정합니다. 그리고 \(1 <= g <= p-1\)인 g를 정합니다. p와 g는 공개되어도 상관없습니다. 누구나 알아도 됩니다.

2) 그 후 Alice는 \(0 <= x < p-1\)인 x를 선택합니다. 이 사이의 수면 아무거나 상관없습니다. 그 후 R1=\(g^{x}\) mod p를 계산합니다. 여기서 mod는 모듈러 연산으로 \(g^{x}\)를 p로 나눈 나머지를 의미합니다. Alice가 정한 x는 Alice 본인만 알아야하는 값입니다. 절대 공개해서는 안되는 값입니다.

3) Bob도 역시 Alice와 마찬가지로 \(0 <= y < p-1\)인 y를 선택합니다. 그 후 R2=\(g^{y}\) mod p를 전달합니다.  y는 Bob만 알고 있어야합니다.

4) 이제 서로 R1과 R2를 당사자들에게 보냅니다. 

5) Alice는 R2\(^{x}\) mod p를 계산합니다. \((g^{y}\) mod p)\(^{x}\) mod p = \((g^{y})^{x}\) mod p = \(g^{yx}\) mod p로 이 값이 결국 대칭키 K가 됩니다.  

6) Bob도 역시 R1\(^{y}\) mod p 를 계산합니다. \((g^{x}\) mod p)\(^{y}\) mod p = \((g^{x})^{y}\) mod p = \(g^{xy}\) mod p로 이 값이 결국 대칭키 K가 됩니다.

결국 둘은 K=(g^{xy}\) mod p 인 대칭키를 갖고 있게 됩니다. 아래의 그림은 그 과정을 나타냅니다.(오타가 있네요. 아래 그림에 Bob이 생성한것은 R1이 아니라 R2입니다.) 

Diffie-Hellman

 

예제

이게껏 설명만 했는데 실제로 계산하는 과정을 보도록 하겠습니다.

Alice와 Bob은 p와 g는 11과 7로 설정했습니다. Alice는 x를 5로 선택하고 Bob은 y를 3으로 선택했다고 치겠습니다. R1=\(g^{5}\) mod p를 보내고, Bob은 R2=\(g^{3}\) mod p으로 하여 서로에게 보냅니다. 이제 Alice는 \((7^{3}\) mod 11)\(^{5}\) mod 11 = \((7^{3})^{5}\) mod 11 = \(7^{15}\) mod 11 의 값 K=10입니다. 이제 Bob도 역시 그렇게 계산을 하는데요. \((7^{5}\) mod 11)\(^{3}\) mod p = \((7^{5})^{3}\) mod 11 = \(7^{15}\) mod 11의 값은 역시 동일한 값으로 10임을 확인할 수 있습니다.

 

 

중간자 공격(Man-In-The-Middle-Attack)

만약 Alice와 Bob 사이에 공격자가 있다면 어떻게 될까요? 어찌 저찌 되어서 Alice가 Bob과 통신해야하는 것을 Eve로 착각했다고 합니다. Bob 역시 Alice와 통신을 해야하는데 Eve가 Alice로 알고 있다고 해봅시다. 그러면 Alice는 R1을 계산해서 Eve에게 주고, Bob도 R2를 계산해서 Eve에게 줍니다. Eve는 자신도 z를 정해서 R3를 양쪽 모두에게 전달해줍니다. 

이렇게 되면 Alice는 R3를 가지고 키 K1을 구할 수 있고 Eve 역시 위의 방법대로 K1을 구할 수 있습니다. Bob도 역시 R3를 가지고 K2를 구할 수 있게 되고 Eve 역시 K2를 구할 수 있습니다. 이제 공격자는 Alice와도 통신할 수 있고 Bob과도 통신할 수 있는 상황이 되었습니다. 공격자가 중간에 위치해서 민감한 정보를 모두 볼 수 있군요. 이런 공격 방식이 중간자 공격이라고 합니다.

man-in-the-middle attack

 

이산대수 공격

만약 Eve가 R1, R2를 가로챌 수 있다고 해봅시다. R1에서 x를 구하고, R2에서 y를 구할 수 있으면 K를 구하는 것을 쉽습니다. 그런 이산대수 공격에 대해서 안전하려면 p를 아주 매우 큰 소수로 정해놓아야합니다. 그래야 x,y를 구하기가 어렵거든요. p-1이 적어도 하나의 큰 소인수(60자리의 10진수 이상)을 가져야합니다. Alice와 Bob은 x,y를 사용 즉시 폐기하는 것이 좋습니다.

 

이상으로 디피-헬만 암호 프로토콜에 대해서 알아보았는데요. 모듈러 연산을 모르신다면 조금 어려울 수가 있는데요. 모듈러 연산에 대해서는 따로 시간을 내어 공부하시는 것이 좋을 듯 합니다. 쉽게 계산하는 규칙들도 존재하거든요. 디피-헬만은 ECC 암호화 알고리즘과 결합하여 사용하기도 합니다. ECDH라고도 하지요. 

DH에서 키를 구하는 문제가 이전 2016년 정보보안 기사 시험에 나온적이 있습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

/etc/passwd(사용자 정보)

보안이나 리눅스를 배우신다면 /etc/passwd에 대한 이해는 필수입니다. 정보보안기사 시험에도 당골손님이기도 하죠.

/etc/passwd에는 시스템에 등록된 사용자의 정보들이 담겨있는 파일입니다. 이 파일을 이용해서 사용자의 계정과 인증을 관리하게 됩니다. 이 파일의 이름이 passwd인데, 여기에 패스워드 정보가 있는건 아니구요. 과거에 사용자의 패스워드를 이곳에 저장을 하였는데, /etc/shadow에 따로 암호화된 패스워드를 저장합니다. 과거에 패스워드를 저장했었던 것이 아직 파일 이름으로 남아 파일명이 passwd입니다.

/etc/passwd를 열어보면 아래와 같이 사용자의 정보들이 있습니다. 

/etc/passwd

 

각 필드들이 의미하는 바가 있는데, 필드 하나하나 무엇을 의미하는지 확인해봅시다. 각 필드는 콜론(:)으로 구분되어 집니다. 

     
        root:x:0:0:root:/root:/bin/bashroot:x:0:0:root:/root:/bin/bash
        

 

필드 설명
사용자 계정명 맨 앞에 필드는 사용자의 계정명을 나타냅니다
패스워드 그 다음의 필드는 패스워드 필드인데, x가 의미하는 바는 사용자의 패스워드가 /etc/shadow에 암호화되어 저장되어있다는 뜻입니다.
UID 사용자의 user id를 나타냅니다. 관리자 계정(Root)은 UID가 0입니다.
GID 사용자의 그룹 ID를 나타냅니다. 관리자 그룹(Root)의 GID는 0입니다.
comment 사용자와 관련한 기타 정보로 일반적으로 사용자의 이름을 나타냅니다.
홈 디렉토리 사용자의 홈디렉토리를 의미합니다. 관리자 계정의 홈 디렉토리는 /root이며, 다른 사용자의 홈 디렉토리는 기본으로 /home/ 하위에 계정명으로 위치합니다.
로그인 쉘 사용자가 로그인시에 사용할 쉘을 의미합니다. 보통 사용자의 쉘은 성능이 우수한 bash쉘을 사용합니다. 로그인이 불필요한 계정도 있는데요. 이때는 이 필드가 /usr/sbin/nologin, /bin/false, /sbin/nologin 등으로 표기됩니다. 이것은 사용자가 아니라 어플리케이션이기 때문이죠. 아래처럼 말이죠.

sshd:x:126:65534::/run/sshd:/usr/sbin/nologin
gnome-initial-setup:x:124:65534::/run/gnome-initial-setup/:/bin/false
gdm:x:125:130:Gnome Display Manager:/var/lib/gdm3:/bin/false


 

 

 

/etc/shadow

/etc/shadow에는 암호화된 패스워드와 패스워드 설정 정책이 기재되어 있습니다. 여기서 관리자 계정과 관리자 그룹만이 이 파일을 읽을 수 있습니다. 이 파일이 만약 평문의 비밀번호의 정보를 가지고 있다면 모든 사용자의 비밀번호가 유출될 수 있습니다.

/etc/shadow

 

/etc/shadow 파일의 root의 정보를 보게되면 아래와 같이 나오게 되는데요. 각 필드에 대해 설명해보도록 하겠습니다.

/etc/shadow field

필드 설명
사용자 계정명 맨 첫 필드는 사용자의 계정명을 뜻합니다.
암호화된 패스워드 두번째 필드는 암호화된 패스워드를 뜻합니다. 이 패스워드는 또 $로 필드가 구분되어 있는데요. 아래처럼 구분이 됩니다.

$algorithm_id$salt$encrypted_password

algorithm_id : 암호학적 해시의 id를 의미하는데요. id는 아래와 같습니다.
1 : MD5(가장 취약한 일방향 해쉬로 요즘에는 쓰지 않습니다.
2 : BlowFish
5 : SHA-256
6 : SHA-512
제 시스템은 SHA-512를 사용하는 것을 알 수 있습니다.

salt : 각 해쉬에 첨가할 랜덤값입니다. 이 랜덤값에 따라서 해시의 값이 바뀌게 됩니다.
encrypted_password : 마지막은 알고리즘과 salt로 패스워드를 암호화한 값입니다.

이 뿐만 아니라 패스워드 필드에 *, !!, 또는 빈값이 설정될 수 있습니다. 이것이 의미하는 바를 알아보겠습니다.
* : 패스워드가 잠긴 상태입니다. 로그인은 불가합니다. 별도의 인증방식을 사용하여 로그인을 할 수는 있습니다.
! : 패스워드가 잠긴 상태이고 로그인을 할 수 없습니다. 또는 사용자를 생성하고 패스워드를 설정하지 않은 상태이기도 합니다.
empty : 패스워드가 설정되지 않았지만 로그인이 가능합니다. 
마지막 변경 마지막으로 패스워드를 변경한 날을 1970년 1월 1일 기준으로 일수도 표시합니다. 여기서 마지막으로 패스워드를 변경한 날은 1970년 1월 1일 이후 18693일이 지났음을 알 수 있습니다.
패스워드 최소 사용기간 패스워드의 최소 사용기간 설정으로 패스워드를 변경한 이후 최소 이 정도의 기간은 써야한다는 것을 의미합니다. 그래서 마지막 변경이 일어난 후 이 일수가 지나기 전에 암호를 변경할 수 없습니다.
패스워드 최대 사용기간 패스워드의 최대 사용기간 설정으로 마지막 패스워드 변경 이후 만료일수를 의미합니다. 패스워드를 변경하지 않으면 공격자가 패스워드를 깰 수도 있으므로 90일을 권장합니다.
경고 패스워드 만료 이전에 경고할 경고 일수를 의미합니다.
비활성화 패스워드가 만료된 이후에 계정이 잠기기 전까지 비활성 일수(date)입니다. 해당 비활성 기간동안에 패스워드를 변경해야 계정이 잠기지 않습니다. 
만료일 계정 만료일 필드입니다. 1970년 1월 1일 기준으로 일수로 표시합니다.

 

이상으로 /etc/shadow와 /etc/passwd에 대해서 알아보았는데, 이 파일은 매우 중요하므로 보안을 배우는 사람은 잘 알아두어야합니다. 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

쉘스크립트 기본

쉘 스크립트는 쉘에게 무슨 명령들을 실행할지 알려주는 스크립트 파일입니다. 여기서는 가장 널리쓰이는 bash 쉘을 사용하는 스크립트를 설명하도록 하겠습니다.

#!/bin/bash

스크립트 최상단에는 항상 이 구문이 적혀있어야합니다. 간단하게 hello,world라는 문자열을 출력하는 스크립트를 만들어봅시다. 파일명은 exam.sh로 정해놓고요.

#!/bin/bash

echo "hello, world"
printf "hello, world"

 

./exam.sh로 스크립트를 실행하면 되는데 혹시 실행이 되지 않는다면 실행 권한이 없어서 그런것이니까 chmod 777 exam.sh로 권한을 바꾸어주면 됩니다. 그 후 결과를 보면 echo는 개행이 되지만 printf는 개행이 되지 않는 다는 것을 알 수 있습니다. 또 C언어 처럼 문자열을 전달할 수도 있는데 포맷 문자 %s를 이용하면 됩니다.

printf "%s, %s" hello, world

 

1) 변수(Variable)

쉘스크립트에서도 변수를 사용할 수 있는데요. 변수명=값 으로 변수를 선언하고, 사용할때는 앞에 $를 붙여줍니다. 중괄호를 이용하는 방법도 있습니다. ${변수}식으로 중괄호로 묶어서 사용합니다.

#!/bin/bash

h="hello"
w="world"
echo "${h}, ${w}"

 

여기서 export를 사용하여 다른 쉘 스크립트에서 사용할 수 있게 합니다. exam.sh는 변수 선언하고 export를, exam2.sh는 그 변수를 출력하는 쉘 스크립트입니다.

#!/bin/bash
#exam.sh
export MY_VAR="reakwon"

./exam2.sh

 

#!/bin/bash
#exam2.sh
echo ${MY_VAR}

 

매개변수

프로그램에서도 실행할때 인자를 주듯 쉘 스크립트도 역시 그렇게 할 수 있습니다. 실행한 스크립트 이름은 ${0}, 그 이후는 전달받은 인자 값들입니다(${1}, ${2}, ...). 아래의 예제코드를 보면서 이해하실 수 있습니다.

#!/bin/bash


echo "script name:${0}"
echo "매개변수 갯수 :${#}"
echo "전체 매개변수  값 : ${*}"
echo "전체 매개변수 값2 : ${@}"
echo "매개변수 1 : ${1}"
echo "매개변수 2 : ${2}"

 

 

 

 

 

 

 

 

예약변수

쉘 스크립트에서 사용자가 정해서 만들 수 없는 이미 정의된 변수가 존재합니다. 그것을 예약 변수라고 하는데요. 몇가지 예약 변수를 보도록 합시다.

변수 설명
HOME 사용자 홈 디렉토리를 의미합니다.
PATH 실행 파일의 경로입니다. 여러분이 chmod, mkdir 등의 명령어들은 /bin이나 /usr/bin, /sbin에 위치하는데, 이 경로들을 PATH 지정하면 여러분들은 굳이 /bin/chmod를 입력하지 않고, chmod 입력만 해주면 됩니다.
LANG 프로그램 실행 시 지원되는 언어를 말합니다.
UID 사용자의 UID입니다.
SHELL 사용자가 로그인시 실행되는 쉘을 말합니다.
USER 사용자의 계정 이름을 말합니다.
FUNCNAME 현재 실행되고 있는 함수 이름을 말합니다.
TERM 로그인 터미널을 말합니다.

 

2) 배열(Array)

쉘 스크립트에서 배열은 1차원 배열만 지원하며 중괄호를 사용해야합니다. 배열 원소는 소괄호안에 공백으로 구분하여 줍니다. 배열 안 원소는 문자열이든 정수든 상관 없이 쓸 수 있는 특징이 있습니다.

#!/bin/bash

arr=("hello" "world" 1 2 3 4 5)

echo "배열 전체 : ${arr[@]}"
echo "배열 원소의 갯수 : ${#arr[@]}"
echo "배열 첫번째 : ${arr}, 혹은 ${arr[0]}"
echo "5번 index를 갖는 배열의 원소 : ${arr[5]}"

arr[5]="five"

echo "5번 index를 갖는 배열의 원소 : ${arr[5]}"

# 5번 원소 해제
unset arr[5]
echo "5번 원소 삭제 후"
echo "5번 index를 갖는 배열의 원소 : ${arr[5]}"
echo "6번 index를 갖는 배열의 원소 : ${arr[6]}"

 

3) 함수(Function)

다른 프로그래밍 언어와 같이 쉘 스크립트에서도 함수를 사용할 수 있습니다. 함수의 정의 형식은 아래와 같습니다. 함수명 앞 function은 써주지 않아도 무관합니다. 주의 할 점은 함수가 호출되기 전에 함수가 정의되어 있어야하며 호출할때는 괄호를 써주지 않고 호출해야한다는 점입니다.

function 함수명(){
 ... 내용 ...
}
#!/bin/bash

function func(){
        echo "func()"
}

#함수 호출
func

 

4) 비교문

쉘스크립트에서 비교문은 약간 특이합니다. 아래와 같은 형식으로 비교문을 사용할 수 있습니다. 

if [ 값1 조건 값2 ]; then
	... 작업 내용 ...
fi

조건은 -eq, -gt, -lt 등이 있는데, 자세한 것은 아래의 코드에서 보도록 합시다.

#!/bin/bash

function func(){
        a=10
        b=5

        if [ ${a} -eq ${b} ]; then
                echo "a와 b는 같다."
        fi

        if [ ${a} -ne ${b} ]; then
                echo "a와 b는 같지 않다."
        fi

        if [ ${a} -gt ${b} ]; then
                echo "a가 b보다 크다."
        fi

        if [ ${a} -ge ${b} ]; then
                echo "a가 b보다 크거나 같다."
        fi

        if [ ${a} -lt ${b} ]; then
                echo "a가 b보다 작다."
        fi

        if [ ${a} -le ${b} ]; then
                echo "a가 b보다 작거나 같다."
        fi

}

#함수 호출
func

 

 

 

 

 

 

쉘스크립트는 명령어를 다루는 스크립트이기 때문에 파일이 존재하는지, 디렉토리가 존재하는지 등 파일과 관련된 조건 여부도 조건문을 통해 알 수 있습니다. 아래는 그것을 정리한 표입니다. 그리고 앞에 느낌표(!)를 써주고 조건을 기재하면 그 조건이 false일때 참이 성립합니다.

파일 관련 조건

조건 설명
if [ -d ${변수} ]; then 
if [ ! -d ${변수} ]; then
${변수}의 디렉토리가 존재하면 참이 성립합니다. 
${변수}의 디렉토리가 존재하지 않으면 참이 성립합니다. 
if [ -e ${변수} ]; then 
if [ ! -e ${변수} ]; then 
${변수}라는 파일이 존재하면 참입니다.
${변수}라는 파일이 존재하지 않으면 참입니다.
if [ -L ${변수} ]; then 파일이 symbolic link이면 참입니다.
if [ -s ${변수} ]; then 파일의 크기가 0보다 크면 참입니다.
if [ -S ${변수} ]; then 파일 타입이 소켓이면 참입니다.
if [ -r ${변수} ]; then 파일을 읽을 수 있으면 참입니다.
if [ -w ${변수} ]; then 파일을 쓸 수 있으면 참입니다.
if [ -x ${변수} ]; then 파일을 실행할 수 있으면 참입니다.
if [ -f ${변수} ]; then 파일이 정규 파일이면 참입니다.
if [ -c ${변수} ]; then 파일이 문자 장치이면 참입니다.
if [ ${변수1} -nt ${변수2}]; then 변수1의 파일이 변수2의 파일보다 최신 파일이면 참입니다.
if [ ${변수1} -ot ${변수2}]; then 변수1의 파일이 변수2의 파일보다 최신이 아니면 참입니다.
if [ ${변수1} -ef ${변수2}]; then 변수1의 파일과 변수2의 파일이 동일하면 참입니다.

 

만약 AND조건과 OR조건을 섞어서 쓴다면 어떻게 할까요? AND는 -a, OR은 -o를 이용해서 조건을 추가할 수 있습니다.

function func(){
        a=aa
        b=bb
        if [ -f ${a} -a -d ${b} ]; then
                echo "a는 파일이고 b는 디렉토리 "
        fi
}

#함수 호출
func

 

또한 여러 조건을 걸려면, 즉, C언어에서 else if역할을 하는 것은 elif라는 것을 사용하여 else if와 똑같은 역할을 할 수 있게 해줍니다. elif[ 조건 ]; then 이런식으로 쓰시면 됩니다.

만약 반복문에서 사용하여 조건이 참일때 반복문을 멈추고 싶을때 break라는 키워드를 사용하여 반복문을 멈출 수 있습니다.

C에서 switch case와 동일한 구문을 하는 문법도 존재합니다. 아래와 같이 case문을 사용하면 됩니다. 각 case의 끝을 보면 세미콜론 2개로 종료하는 것을 볼 수 있습니다.

#!/bin/bash

case ${1} in
        "linux") echo "리눅스" ;;
        "unix") echo "유닉스" ;;
        "windows") echo "윈도우즈" ;;
        "MacOS") echo "맥OS" ;;
        *) echo "머야" ;;
esac

5) 반복문

반복문 중 for문에 대한 설명은 예제와 같이 보는 것으로 하겠습니다. 

#!/bin/bash

function func(){
        echo "사용예1"
        for i in 1 2 3 4 5
        do
                echo "${i}"
        done

        echo "사용예2"
        list="1 2 3 4 5"
        for i in ${list}
        do
                echo "${i}"
        done

        echo "사용예3"
        for i in {1..5}
        do
                echo "${i}"
        done

        echo "사용예4: 크기를 2만큼 증가시키면서 출력"
        for i in {1..5..2}
        do
                echo "${i}"
        done

        echo "사용예5: 배열을 이용"
        arr=(1 2 3 4 5)
        for i in "${arr[@]}"
        do
                echo "${i}"
        done


        echo "사용예6: C와 유사한 형식의 for문"
        for ((i=0; i<5; i++)); do
                echo "${i}"
        done
}

#함수 호출
func

 

 

 

 

 

6) 명령어의 종료 코드(exit)

만약 명령어가 실패할 경우 동작을 다르게 할것이라면 어떻게 할까요? 명령어의 종료 코드를 얻어오면 됩니다. 명령어의 종료 코드는 $?에 가장 최근 실행한 명령어의 종료 코드가 있습니다. 정상적인 종료는 0이 반환되는데, 만약 0이 아닌 값으로 반환되면 오류라고 판단하시면 됩니다. 여기에 간단한 오류를 내는 c파일(main.c)을 작성하고 gcc로 컴파일(gcc main.c)합니다. 이 c파일은 실행시 전달인자가 2개 보다 작으면 exit으로 코드 16을 반환합니다.

#include <stdlib.h>
#include <stdio.h>
int main(int argc, char* argv[]){
        if(argc<2){
                fprintf(stderr,"인자는 2개 이상이어야 합니다.\n");
                exit(16);
        }
}

 

그리고 쉘스크립트를 아래와 같이 수정해서 오류의 코드가 얼마인지 확인해보면 우리가 exit에 넘겨준 16이 찍히는 것을 확인할 수 있습니다.

#!/bin/bash

function func(){
        ./a.out
        echo "오류 코드 ($?)"
}

#함수 호출
func

 

./a.out에 인자를 하나 넣어서 다시 실행시켜보세요. 0이 종료코드로 반환 될 것입니다. 이렇게 이전에 명령어의 결과를 봐야하는 경우 $?으로 판단하여 분기시킬 수 있습니다.

이렇게 종료 반환 코드는 명령어 뿐만 아니라 쉘 스크립트 자체도 반환할 수 있습니다. 즉, 쉘 스크립트의 수행이 실패했는지 알려주기 위해서는 exit 명령어를 사용하면 됩니다. 아래의 예가 그 사용법을 보여줍니다. 우선 exam2.sh라는 파일을 만들어봅시다. 이 쉘 스크립트 파일은 exam.sh에서 실행할 예정입니다. 별거없이 16의 종료코드를 반환하며 끝이 납니다. 

#!/bin/bash

exit 16

 

이 쉘 파일을 exam.sh에서 실행시켜볼까요?

#!/bin/bash

function func(){
        ./exam2.sh
        echo "오류 코드 ($?)"
}

#함수 호출
func

 

확인해보면 우리가 기재했던 16이 출력되는 것을 확인할 수 있습니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

리눅스 커널(Kernel)

리눅스 커널은 하드웨어와 가장 가까이 있는 일종의 프로그램입니다. 가장 핵심적인 프로그램으로 사용자는 커널을 통해서 하드웨어를 제어합니다. 함부로 하드웨어를 직접 만졌다가는 돌아올 수 없는 강을 건너게 될 수도 있습니다. H/W에는 CPU, 메모리(RAM), 하드디스크(HDD), 기타 입출력 장치 등 많은 것들이 있는데 실제 직접 조작할 수도 없고 해서도 안됩니다. 그래서 커널이 존재하게 되는것이지요. 이 처럼 커널은 시스템의 자원을 관리하는 운영체제 그 자체로 보시면 됩니다. 

윈도우즈도 역시 커널을 가지고 있는데, 사용자가 커널의 코드를 직접 들여다 볼 수 없고 거기에 맞는 모듈(Module)을 만들어거 끼워넣을 수 없죠. 윈도우즈는 커널 내부를 공개하고 있지 않기 때문인데요. 그래서 윈도우즈 커널을 수정하려면 오직 MS사 밖에 할 수 없습니다. 리눅스는 윈도우즈와는 다르게 커널 내부 소스를 전부 공개합니다. 그래서 커널을 잘 아는 사람은 거기에 맞게 모듈이라는 커널의 부분을 끼워넣어서 변경할 수 있습니다. 

이전에 리눅스는 커널에 조금이라도 변경이 생기면 수정 후 전체 커널 컴파일을 해야했는데, 이 모듈이라는 개념이 도입된 후 필요한 커널의 부분만 수정하고 다시 끼울 수 있으면서 커널 컴파일하는 모든 과정을 수행하지 않아 시간이 획기적으로 줄어 들 수 있습니다. 현재 적재된 커널 모듈을 보려면 lsmod를 이용해서 볼 수 있고 모듈을 설치할때는 insmod를 이용하면 되는데, 물론 아무나는 안됩니다. 커널 프로그래밍을 이용하기 때문에 아무나 건드리면 시스템이 멈출 수 있습니다.

쉘(shell)

쉘은 사용자의 응용프로그램과 커널 사이에 위치해있으며 응용프로그램의 명령어와 커널이 대화를 하도록 만들어줍니다. 그래서 명령어 해석기라는 부릅니다.

자, 우리가 명령어를 입력하게 되면 컴퓨터에서는 쉘(shell)이 이 명령어를 받아 해석하여 커널(kernel)에게 보내면 커널은 우리가 내려주었던 동작을 하게 됩니다. 그래서 그에 대한 결과를 사용자에게 전달하려고 다시 쉘에 응답을 보내고 쉘을 거쳐 사용자에게 도달합니다. 여기서 쉘도 프로그램입니다.

쉘 종류는 여러가지고 존재합니다. 

Bourne Shell (/bin/sh)

본쉘이라고 합니다. 유닉스 최초의 쉘이기도 하지요. 최조의 쉘이라서 미흡한 기능이 많은데요. 여기서 일반 유저의 쉘 프롬프트는 $, root사용자의 프롬프트는 #으로 나타냅니다.

Bash, Bourne Again Shell (/bin/bash)

현재 리눅스에서 표준으로 채택된 쉘입니다. 여러분에게 가장 친숙한 쉘이라고 생각하시면 됩니다. 여러분이 명령을 칠때 명령어 다 쓰기 싫어서 tab쓰시죠? 그게 원래는 리눅스 자체의 기능이 아니고 bash의 기능입니다. 그 밖에도 아래와 같은 특징이 있습니다. Mac 운영체제에서도 bash가 쓰입니다.


 - History(방향키 ↑↓)
 - Alias
 - 자동 완성 (tab)
 - 연산
 - Job Control
 - 그 외

 

C shell (/bin/csh)

C언어를 기반으로 만들어진 쉘입니다. 쉘 스크립트가 C언어로 작성하는 것처럼 작성할 수 있습니다.

 

 

쉘을 이해해보자

이 외에도 쉘의 종류는 여러가지가 있는데요. 실제 시스템에서 지원하는 쉘을 보기 위해서 /etc/shells를 열어보시면 됩니다. 아래는 저의 시스템에 있는 shell들입니다.


/home/ubuntu# cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash

그리고 사용자가 어떤 쉘을 쓰고 있는지 보고 싶으시다면 SHELL 환경변수를 확인하면 됩니다. echo $SHELL 으로 확인하세요. 저는 bash쉘을 사용하고 있군요.


/home/ubuntu# echo $SHELL
/bin/bash

 

여기서 bash쉘에서 지원하는 기능이 sh(본쉘)에서도 먹힐까요? 사용하는 bash쉘을 본쉘로 바꿔보도록 하겠습니다. 단순히 sh명령으로 쉘을 실행해봅시다. 그리고 명령어를 아무거나 처보세요. 그리고 우리가 이전의 명령어를 치려고 위 화살표(↑)를 누르면 이상한 문자(^[[A)가 쓰여지게 되어 이전의 명령어를 수행할 수는 없습니다. 결국 sh는 히스토리 기능을 지원하지 못하며, 정확히 얘기해서 저의 명령을 해석하지 못한다는 것입니다. bash에서는 ~/.bash_history에 그 동안의 명령어들 내역을 보관하고 있는데, 본쉘은 ~/.bash_history가 무엇인지도 모르죠.


/home/ubuntu# sh
# ls
공개  다운로드  문서  바탕화면  비디오  사진  음악  템플릿
# pwd
/home/ubuntu
# ^[[A

 

보통 bash에서 ll은 alias로 ls -alF의 명령어 별명으로 지정합니다. 지금 bash에서 ll을 치면 파일들의 목록이 자세히 나오게 되는데요. ll을 sh상에서 실행하면 실행이 되지 않습니다. bash에서 ll이 수행되지 않는다면 alias지정이 되지 않은 것인데요. vi로 ~/.bashrc를 열고 하단에 alias ll='ls -alF'를 써주시고 source ~/.bashrc를 수행하면 이제 ll이 수행될 것입니다. bashrc는 bash쉘의 설정파일입니다. 

 
     80 # some more ls aliases
     81 alias ll='ls -alF'
     82 alias la='ls -A'
     83 alias l='ls -CF'
     84

그렇다면 사용자가 접속할때 bash말고 다른 쉘을 사용하고 싶다면 어떻게 할까요? 시스템에서 root로 접속시 기본적으로 사용할 쉘이 어디에 기록되어있냐면 /etc/passwd에 기록되어있습니다. cat /etc/passwd | grep root의 결과가 아래와 같습니다. 리눅스를 조금 공부하셨다면 /etc/passwd에서 각 필드가 무엇을 뜻하는지는 아실겁니다. 

root:x:0:0:root:/root:/bin/bash

맨 오른쪽에 보이는 것이 접속 시 적용할 쉘인데요. 바꾸고 싶다면 chsh -s [shell path]를 입력하면 됩니다. 예를 들면 chsh -s /bin/sh 이렇게 쓰고 재접속을 하면 적용된 쉘로 바뀝니다. 

쉘에서 쓰는 명령어들을 하나의 대본으로 만들어 놓을 수 있는데요. 예를 들어 어느 경로에 들어가서 디렉토리를 만들고 거기에 파일을 만들고 파일의 내용을 어떤 내용으로 기록하고, 그런 작업들 말이에요. 그러려면 사용자가 수동으로 cd를 입력하고 mkdir하고 touch해서 파일을 만들고 그 내용을 echo '블라블라' > file 해서 입력하고 해야겠지요. 하지만 이런 대본(script)를 만들어서 쉘에게 알려주면 일련의 명령을 쉘이 알아서 수행해줍니다. 이것을 우리는 쉘 스크립트라고 하며 확장자가 .sh로 끝나는 파일입니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

스케줄링의 개념

단일 처리 시스템에서는 실행 중인 프로세스(A)가 존재하는데 다른 프로세스(B)가 입출력을 요청하면 그 프로세스(B)는 이전의 프로세스(A)의 자원을 놓을때까지 대기하고 있어야합니다. 하지만 다중 프로그래밍에서는 여러 프로세스들이 동시에 돌아갈 수 있으며, 프로세스가 자원(프로세서 등)을 요청하면 운영체제는 그 자원을 적절히 분배하여 프로세스에게 할당합니다. 그래서 다음과 같은 장점을 얻을 수 있습니다.

1. CPU의 활용 극대화

2.프로세스 처리율(시간 당 작업량)을 늘릴 수 있습니다.

프로세스는 필요한 자원을 할당받기 위해 큐에 대기합니다. 그래서 그 큐에 있는 프로세스를 어떻게 스케쥴링하는지가 프로세스 스케줄링 알고리즘이라고 합니다. 그레서 어떤 스케줄링 알고리즘이 있는지 봅시다. 

잠깐 프로세스 스케줄링에 관한 용어 몇가지만 알아보고 가도록 합시다. 대기 시간, 실행 시간, 반환 시간입니다. 

용어 설명
대기 시간 자원의 할당을 대기하는 시간을 의미합니다.
실행 시간 실제로 프로세스가 자원을 할당받은 다음 작업을 수행하는 시간을 의미합니다.
반환 시간 작업을 완료하는데 소요되는 전체 시간으로 대기 시간과 실행 시간을 모두 포함합니다. 

 

1 선입선처리 스케줄링(First Come First Served, First In First Out)

선입선처리(FCFS, FIFO) 알고리즘은 말 그대로 먼저 요청한 프로세스가 먼저 자원을 제공받으며 이미 사용중이라면 사용이 끝날때까지 기다려야하는 스케줄링 방식입니다.

프로세스 5개가 있고, 각가 P1의 실행시간 10, P2의 실행시간 5, P3의 실행시간 6, P4의 실행시간 9, P5의 실행시간 10이라고 할때 반환 시간을 알아보도록 하죠.

프로세스 도착 시간 실행 시간
P1 0 10
P2 1 28
P3 2 6
P4 3 4
P5 4 14

 

먼저 요청한 프로세스가 먼저 제공받으니 P1부터 차례대로 자원을 할당받습니다. 그래서 프로세스의 반환시간과 대기시간을 구하면 아래와 같습니다.

로세스 반환시간 대기시간
P1 10 0
P2 37 9
P3 42 36
P4 45 41
P5 58 44
평균 평균 반환 시간
(10+37+42+45+58)/5 = 38.4
평균 대기 시간
(0+9+36+41+44)/5 = 
26
장점 1) 스케줄링이 단순합니다.
2) 모든 프로세스가 실행될 수 있습니다.
3) 프로세서가 지속적으로 유용한 프로세스를 수행하여 처리율이 높습니다.
단점 1) 비선점방식의 스케줄링이므로 대화형 작업에는 부적합합니다.
2) 만약 어떤 프로세스의 수행시간이 길면 대기시간이 늘어납니다. 그래서 짧고 간단한 작업은 계속 기다려야합니다.

2. 최소 작업 우선 스케줄링(Shortest Job First)

프로세스의 실행 시간을 이용하여 가장 짧은 시간을 갖는 프로세스가 먼저 자원을 할당받는 방식입니다. 이 방식은 선점할 수도 있는 스케줄링 방식입니다. 이전에 FIFO방식은 중간에 다른 프로세스가 들어오면 그 프로세스는 대기해야했죠? 이 스케줄링 방식은 선점 또는 비선점이 가능합니다. 위와 같은 프로세스라고 할때 비선점형 SJF 스케줄링의 결과는 아래와 같습니다.

로세스 반환시간 대기시간
P1 10 0
P2 61 33
P3 18 12
P4 11 7
P5 30 16
평균 평균 반환 시간
(10+61+18+11+30)/5 = 26
평균 대기 시간
(0+33+12+7+16)/5 = 13.6

 

자 P1이 먼저 요청해서 자원을 할당받아 놓은 상태인데, P2가 도착합니다. 비선점형이기 때문에 P2는 우선 기다리고 있는데 P1의 작업이 끝나기 전에 P3, P4 ,P5의 요청이 도착하네요. 그러면 실행 시간이 가장 작은 P4가 자원을 할당받아 쓰고 그 다음인 P3이 자원을 할당받아 사용합니다. 이렇게 되면 위와 같은 결과가 도출되지요.

그렇다면 비선점형 SJF는 어떨까요? 아래의 표가 선점형 SJF의 결과입니다.

로세스 반환시간 대기시간
P1 20 10
P2 61 33
P3 10 5
P4 4 0
P5 30 16
평균 평균 반환 시간
(20+61+10+4+30)/5 = 25
평균 대기 시간
(10+33+4+0+16)/5 = 12.6

이해를 돕기 위해서 아래의 간트 차트를 보세요.  P1이 먼저 도착해서 수행하고 있는 와중에 P2가 도착하는데요. P2는 수행되어야할 시간이 P1보다 크니 P1은 계속 작업을 수행합니다. 그러다가 P3가 도착하는데 P1이 수행해야할 시간보다 P3의 수행시간이 더 짧으니 P3가 작업을 수행합니다. 그런데 P4가 다음에 도착하네요. P4가 더 적은 시간을 갖으니 P4를 수행합니다. 이때 P4가 끝나면 남아있는 프로세스 중에서 가장 적은 수행시간을 갖는 P3의 작업을 이어서 하게 됩니다.

장점 1) 항상 짧은 작업을 먼저 처리하게 되니까 평균 대기시간이 가장 짧습니다.
단점 1) 수행시간이 긴 작업은 짧은 작업에 밀려 기아가 발생합니다.
2) 실행 시간을 예측할 수 없어 실용적이지 못합니다.
3) 짧은 작업이 먼저 실행되므로 공정하지 못한 정책입니다.

 

 

 

3. 우선순위 스케줄링

우선순위 스케줄링은 프로세스마다 우선순위라는 속성이 붙게 됩니다. 우선순위 스케줄링도 역시 선점, 비선점형으로 스케줄링이 가능합니다. 숫자가 높을 수록 우선순위가 높고 만약 우선순위가 같다면 FIFO방식으로 동작합니다.

프로세스 도착 시간 실행 시간 우선순위
P1 0 10 3
P2 1 28 2
P3 2 6 4
P4 3 4 1
P5 4 14 2

 

그래서 아래는 선점형에서의 결과를 나타낸 표입니다. 약간 헷갈릴 수 있을텐데요. P1 먼저 수행하다가 P3가 오면 우선 순위가 P3가 더 높으니까 P1은 대기하고 P3가 작업을 수행합니다.

로세스 반환시간 대기시간
P1 16 6
P2 43 15
P3 6 0
P4 59 55
P5 54 40
평균 평균 반환 시간
(16+43+6+59+54)/5 = 35.6
평균 대기 시간
(6+15+0+55+40)/5 = 23.2

다음은 비선점형일때의 결과 표입니다.

로세스 반환시간 대기시간
P1 10 0
P2 43 15
P3 14 8
P4 59 55
P5 54 40
평균 평균 반환 시간
(10+43+14+59+54)/5 = 36
평균 대기 시간
(0+15+8+55+40)/5 = 23.6

 

만약 우선순위가 낮은 프로세스가 높은 프로세스에 의해 실행이 되고 있지 않은 상황이라면 어떻게 될까요? 이때는 그 프로세스의 우선순위를 점차 높여 처리받게끔 하는 에이징이라는 기법을 사용합니다.

장점 1) 각 프로세스의 중요성에 따라서 작업을 수행하기 때문에 합리적입니다.
2) 실시간 시스템에서 사용가능합니다.
단점 1) 높은 우선순위를 갖는 프로세스가 계속적으로 스케줄링이 되면 우선순위가 낮은 프로세스는 자원을 할당받지 못하기 때문에 기아가 발생할 수 있습니다.

4. 라운드 로빈 스케줄링(Round-Robin)

라운드 로빈 스케줄링은 시분할 시스템을 위해 설계된 스케줄링 기법입니다. 이 스케줄링은 작은 단위의 시간인 시간 할당량(Time-Slice)을 정의하여 그 시간 만큼 자원을 할당하는 방식입니다. 그래서 그 시간안에 작업을 끝내지 못하면 다음 프로세스가 다시 그 시간만큼 자원을 할당받아씁니다. 여기서 시간 할당량을 5로 정하고 간트 차트와 결과를 보면 아래와 같습니다. 

각 프로세스들은 공정하게 5만큼의 시간 동안 작업을 진행하는 것을 볼 수 있네요. 

로세스 반환시간 대기시간
P1 29 19
P2 61 33
P3 33 27
P4 16 7
P5 45 31
평균 평균 반환 시간
(29+61+33+16+45)/5 = 36.8
평균 대기 시간
(19+33+27+7+31)/5 = 23.4
장점 1) 모든 프로세스가 공정하게 스케줄링을 받을 수 있습니다.
2) 실행 큐에 프로세스의 수를 알고 있을때 유용합니다.
3) 프로세스의 짧은 응답시간을 갖고 최악의 응답시간을 알 수 있습니다.
4) 평균 대기시간이 FIFO와 SJF보다 적습니다.
단점 1) 성능은 규정 시간량의 길이에 따라 달라지므로 작업이 비슷한 길이가 좋은데, 너무 길면 FIFO로 변하고 짧으면 문맥 교환(Context Switching) 비용이 증가합니다.
2) 하드웨어적 타이머가 필요합니다.
3) 미완성 작업은 규정 시간량(시간 할당량)을 마친 후 프로세서를 기다리니까 평균 처리 시간이 높습니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

보안의 3대 요소

컴퓨터 정보 보안에 대해서 진지하게 공부하기로 마음 먹었다면 꼭 알고 있어야 할 개념이 있습니다. 바로 정보보안의 세 가지 목표입니다. 개념은 어렵지 않으니 차근차근 읽어보면 이해하실 수 있을 겁니다.

기밀성(Confidentiality) 김일성아닙니다.

기밀성이라하면 통신하는 당사자만이 아는 비밀을 말하는데 정보보안에서는 이것을 기밀이라고 합니다. 정보보안에서 가장 많이 요구하는 조건이 바로 기밀이 보장되는 기밀성입니다. 군대나 은행, 산업체의 조직에서 기밀성이 필수적으로 요구되며 정보의 보관뿐만 아니라 정보의 전송에도 적용됩니다. 기밀성을 지키기 위해서는 데이터를 다른 사람들이 이해하지 못하도록 암호화하는 방법이 있습니다.

보안에서 기밀성을 위협하는 공격은 무엇이 있을까요? 간단하게 생각하면 남의 말을 엿듣는거나 훔치는 것이라고 생각하시면 이해하기 쉽습니다. 간단하게 몇 가지 알아보도록 해보지요.

- 스누핑(Snoofing)

스누핑은 비인가된 사용자가 중요 데이터에 접근하는 것이나 탈취를 의미합니다. 예를 들면, 인터넷으로 전성되는 파일이 기밀정보를 담고 있는데, 나쁜 노무 자식이 그 정보를 가로채고 자신의 이익을 위해 사용할 수 있지요.

- 트래픽 분석(Traffic Analysis)

데이터를 이해할 수 없더라도 공격자가 온라인 상에 전송되는 트래픽을 분석함으로써 다른 정보를 얻을 수 있습니다. 예를 들면, 공격자는 송수신자의 ip, 또는 이메일 주소, 또는 전송 성향을 추측하는데 도움이 되는 질의, 응답을 수집할 수 있습니다.

무결성(Integrity)

변경이 허락된 사람에게서 인가된 메카니즘을 통해서만 이뤄져야하는 것을 의미합니다. 정보는 시시각각 변화되는데, 이것이 인정된, 인가된 사람만 변경하는 것을 보장해야합니다. 허가되지 않은 사람이 변경했을때 이를 즉각 알 수 있어야합니다. 무결성을 위협하는 공격은 변경(Modification), 가장(Masquerading), 재연(Replaying), 부인(Repudiation)이 있습니다.

- 변경(Modification)

공격자는 정보를 가로채서 자신에게 이익이 될 수 있게 조작하는 것을 의미합니다. 원래는 송신자에게 줘야할 데이터를 중간에 공격자가 가로채서 자신의 주소로 바꾸거나 하는 것이 있습니다.

 

 

- 가장(Masquerading)

공격자가 인가된 사용자인척 하는 것을 의미합니다. 정보보안에서 스푸핑(Spoofing)이라는 공격 기법이 있는데요. IP나 MAC을 인가된 시스템의 주소로 변경하여 인가된 자인 척 정보를 획득합니다.

- 재연(Replaying)

재연이라는 것은 우선 공격자가 정보를 가로채고 난 이후 나중에 그 정보를 다시 사용함을 의미합니다. 즉, 공격자가 우선 전송된 데이터를 가지고 있다가 자신이 필요할 타이밍에 그 정보를 다시 보내는 것이죠. 예를 들어, 어떤 사람이 공격자에게 송금하라고 하는 데이터를 공격자가 가지고 있는데, 이 정보를 계속 이용해서 그 사람의 계좌에서 계속 돈을 공격자에게 보내는 공격이 있습니다. 그래서 재연이라는 공격을 막기위해 OTP(One-Time Password)를 사용합니다.

- 부인(Repudiation)

현실에서는 구라라고 하죠. 정보보안에서 부인이라함은 정보를 전송하거나 변경했을때 그 사실을 인정하지 않는 것을 말합니다. 현실에서 나중에 거짓말하지 못하도록 서명이나 도장을 찍듯, 정보보안에서도 디지털화된 서명(Digital Signature)이 적용됩니다.

가용성(Availability)

가용성은 정보가 사용가능해야한다는 것입니다. 중요한 정보를 사용하지 못 할 경우 심각한 피해를 입을 수 있으므로 정보에 대한 접근이나 사용이 인가된 자에 의해서 사용이 가능해야합니다. 가용성을 위협하는 공격은 아예 그 시스템을 마비시키는 서비스 거부 공격(DoS)이 있습니다. 한번쯤 어디선가 들어보셨죠?

- 서비스 거부(Denial of Service)

서비스 거부 공격은 가용성을 무너뜨리는 가장 일반적인 형태의 공격입니다. 이 공격을 통해서 서비스를 느리게 만들거나 아예 시스템을 마비시키는 공격입니다. 여러분은 이미 경험해본적이 많을 텐데요. 수강 신청 기간에 웹 페이지가 마비되는 현상을 경험해본적 있으시죠? 이런 현상은 공격이라고 보기는 어렵지만 시스템을 이렇게 만드는 것이 DoS 공격입니다. 여러분도 만들 수 있는데요. 무한 루프를 꾸준히 생성하는 프로세스를 만들 수도 있지요.

 

보안 서비스(Security Services)

ITU-T에서는 이러한 보안의 세 가지 목표를 달성하기 위해서 정보 보안에서는 5가지 서비스를 정의하게 됩니다.

 

 

 

데이터 기밀성(Data Confidentiality)

데이터 기밀성은 공격자가 데이터를 볼 수 없게 보호합니다. 스누핑과 트래픽 분석 공격을 막기 위해서 고안되었습니다. 간단하게 남들이 보지 못하게 만드는 것으로 수 많은 암호화 알고리즘을 사용하여 달성할 수 있습니다. 암호화에는 크게 대칭 암호, 비대칭 암호 알고리즘이 존재하지요. 데이터 기밀성은 스누핑(Snoofing)과 트래픽 분석 (Traffic Anaysis)을 막을 수 있습니다.

데이터 무결성(Data Integrity)

데이터의 무결성은 앞서 말한 변경, 삽입, 삭제, 재연 등으로부터 정보를 보호하는 것으로 메시지의 일부 또는 전체를 보호합니다. 예를 들면 암호학적 해시 함수를 통해서 데이터의 해시값을 만들어 전송하고 수신받는 사람은 전송받은 데이터를 기반으로 해시값을 생성하여 비교해보는 방법이 있습니다. 간단하게 암호학적 해시라함은 데이터를 암호학적 해시함수를 통해서 1:1 대응되는 다른 데이터로 만드는 암호학적 기법입니다. 이러한 암호학적 해시는 여러 해시 알고리즘이 존재합니다.

인증(Authentication)

그 사람이 인가된, 허가된 사람이 맞는지, 또는 데이터가 원하는 데이터가 맞는지확인하는 메커니즘을 사용합니다. 연결 지향(Connection-Oriented) 통신에서 연결이 되었을때 송신자 또는 수신자에 인증이 필요할 수 있습니다. 또는 비연결 지향(Connectionless) 통신에서는 그 데이터의 출처를 인증이 필요할 수 있습니다.

부인봉쇄(Nonrepudiation)

데이터의 송신자가 전송한 데이터가 맞다는 사실, 또는 수신자가 데이터를 수신했다는 사실을 거부할 수 없게 만드는 정보 보안 서비스입니다.

예를 들어, 만약 서버에서 이상한 IP주소로 공격이 발생했을때 시스템에 남아있는 로그를 통해서 그 IP에서 공격 사실을 부인봉쇄할 수 있습니다. 그러니 서버에서는 공격 탐지와 사실을 확인하기 위해 다양한 로그를 남깁니다.

접근제어(Access Control)

비인가된 접근을 제어하여 데이터를 보호하는 보안 서비스를 말합니다. 예를 들어 리눅스 시스템에서 파일의 주인은 데이터를 쓰고(Write) 읽고(Read) 실행(Execute)할 수 있고, 같은 그룹 사용자는 파일을 읽고 실행하는 권한만 있으며, 그 외의 사람은 읽기만 할 수 있습니다

 

 

.

반응형
블로그 이미지

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

와나진짜

,

 

 

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

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

와나진짜

,