시스템 버스, I/O 및 인터럽트

1. 시스템 버스

1-1. 시스템 버스의 조직

  • 시스템 버스에 접속되는 모든 요소들은 버스를 통하여 상호간에 정보를 교환하고, 동작 시간을 조정하기 위한 클록 신호 전송
  • 시스템 버스를 구성하는 선들의 수는 한 번에 전송하는 데이터 비트들의 수가 기억장치 비트들의 수 및 제어 신호들의 수에 따라 결정
  • 데이터 버스: 시스템 요소들 사이에 데이터를 전송하는 데 사용되는 선들의 집합
    • CPU가 8비트씩 읽어오면 8개의 선, 32비트씩 읽어오면 32개의 선이 필요
    • 데이터는 CPU-기억장치, CPU-I/O장치 및 기억장치-I/O 장치 사이에 양방향으로 전송되기 때문에, 데이터 버스는 양방향 전송을 지원해야 함
  • 주소 버스: CPU가 기억장치나 I/O 장치에 액세스할 때 주소 비트들을 전송하는 데 사용되는 선들의 집합
    • 주소는 CPU에 의해서만 발생되기 때문에 단방향 전송 기능만 있으면 됨
    • 주소 버스의 폭은 CPU가 주소 지정할 수 있는 전체 기억장치 용량을 결정
  • 제어 버스: 제어 신호들을 전송하기 위한 선들의 집합
    • ex) 기억장치 쓰기/읽기 신호, I/O 쓰기/읽기 신호
  • 버스 마스터: 시스템 버스 사용의 주체가 되는 요소
    • 일반적인 컴퓨터 시스템에서는 CPU와 I/O 제어기 등이 버스 마스터
    • 동기식 버스 시스템에서는 기억장치 모듈도 버스 마스터가 될 수 있음
  • 두개 이상의 마스터들이 동시에 버스를 사용하고자 할 때는 순서대로 사용하도록 버스 중재를 해주어야 함
  • 중재 버스의 종류
    • 버스 요구 신호: 버스 마스터가 버스 사용을 원하고 있음
    • 버스 승인 신호: 버스 사용을 요구한 마스터에게 사용을 허가
    • 버스 사용 중 신호: 현재 어떤 마스터가 버스를 사용하고 있는 중
  • CPU와 I/O 장치 간의 비동기적 동작을 지원하는 인터럽트 메커니즘을 위한 제어 신호
    • 인터럽트 요구 신호
    • 인터럽트 확인 신호
  • 버스 대역폭: 버스를 통하여 단위시간 당 전송할 수 있는 데이터량으로서, 단위는 초당 바이트 수로 나타냄

1-2. 시스템 버스의 기본 동작

  • 버스 상의 모든 동작들은 쓰기 동작과 읽기 동작으로 구분
  • 동기식 버스: 모든 버스 동작들이 발생하는 시간이 공통의 버스 클록을 기준으로 결정되는 버스
    • 인터페이스 회로가 간단하는 장점
    • 버스 클록의 주기가 가장 오래 걸리는 버스 동작의 소요 시간을 기준으로 정해져야 하기 때문에, 클록 주기보다 짧은 시간이 걸리는 버스 동작의 경우 동작이 완료된 후에도 기다려야 한다는 단점
  • 비동기식 버스: 버스 동작들의 발생 시간이 다른 버스 동작의 발생 여부에 따라 결정되는 버스
    • 동기식 버스에서와 같이 낭비되는 시간이 없다는 장점
    • 연속적 동작들을 처리하기 위한 인터페이스 회로가 복잡해지는 단점

2. 버스 중재

  • 버스 경합이 발생한 경우 버스 마스터들 중에서 한 개씩 선택하여 순서대로 버스를 사용할 수 있게 해주는 행위
  • 버스는 Fair하게 배분되어야 하며, 배분으로 인한 Starvation 현상이 없어야 함

2-1. 병렬 중재 방식

각 버스 마스터가 독립적인 버스요구 신호를 버스 중재기로 보내며, 별도의 버스 승인 신호를 받는 방식

1) 중앙집중식 고정-우선순위 방식
  • 각 버스 마스터에 지정된 우선순위가 변하지 않는 고정-우선순위 방식과 우선순위가 계속 변하는 가변-우선순위 방식이 존재
  • 각 버스 마스터는 자신의 버스 요구(BREQ)선을 가지며, 이들은 모두 하나의 버스 중재기로 접속
  • 버스 중재기는 한 개 이상의 버스 요구 신호를 받아서, 그 중 우선순위가 가장 높은 마스터의 버스 승인(BGNT) 신호
  • BBUSY 신호는 어떤 버스 마스터가 버슬르 사용하고 있는 중이라는 것을 의미
  • 각 마스터에 대한 BGNT 신호는 더 높은 우선순위를 가진 마스터가 버스 요구를 발생하지 않은 상태에서 BREQ를 1로 세트했을 때만 활성화 가능

2) 분산식 고정-우선순위 방식
  • 모든 버스 마스터들이 중재기를 한 개씩 가지고 있음
  • 자신보다 더 높은 우선순위를 가진 버스 마스터들의 버스 요구 신호들을 입력으로 받아 검사하고, 그들 중 어느 것도 버스 사용 요구를 하지 않은 경우에만 자신의 버스 승인 신호를 셋
  • 분산식 중재 방식은 중앙집중식에 비하여 중재 회로가 간단하기 때문에 동작 속도가 더 빠르다는 장점
  • 그러나, 고장을 일으킨 중재기를 찾아내기가 힘들며, 한 중재기의 고장이 전체 시스템에 동작에 영향을 줄 수도 있다는 단점 존재

3) 가변 우선순위 방식
  • 버스 중재기가 시스템의 상태 또는 조건에 따라 각 버스 마스터들의 우선순위를 계속 바꾸어 줌
  • 중재 회로는 더 복잡해지지만, 버스 마스터들이 버스를 균등하게 사용할 수 있게 됨
  • 최상위 우선순위를 가진 마스터가 버스를 독점하거나, 최하위 우선순위를 가진 마스터가 오랫동안 버스를 사용하지 못하는 기근 현상의 방지를 위한 방식
  • 가변 우선순위 알고리즘
    • 회전 우선순위
        1. 중재 동작이 끝날 때마다 모든 마스터들의 우선순위가 낮아지고, 가장 우선순위가 낮았던 마스터가 최상위 우선순위를 가지는 방법
        1. 버스 사용 승인을 받은 마스터는 최하위 우선순위를 가지며, 바로 다음에 위치한 마스터가 최상위 우선순위를 가지도록 하는 방법
    • 임의 우선순위: 각 중재 동작이 끝날 때마다 마스터들의 우선순위가 난수 발생기에 의해 임의로 정해지는 방식
    • 동등 우선순위: 모든 마스터들이 동등한 우선순위를 가지는 방식
    • 최소-최근 사용: 최근 가장 오랫동안 버스를 사용하지 않은 버스 마스터에게 최상위 우선순위를 할당하는 방식

2-2. 직렬 중재 방식

버스요구 신호와 버스승인 신호가 하나씩 있으며, 각 신호 선이 모든 버스 마스터들 간에 직렬로 접속되는 방식

1) 중앙집중식 직렬 중재 방식

  • 하나의 중재 신호선에 의해 모든 버스 마스터들이 직렬로 연결되어 데이지 체인형태를 이룸
  • 마스터들의 우선순위는 버스 중재기를 시작점으로 하여 승인 신호 선이 연결된 순서대로 정해짐(중재기에 가까운 것이 높은 우선순위)
  • 어떤 마스터도 버스를 사용하지 않을 때만 버스 중재기가 승인 신호를 발생하도록 설계해야 함
    1. BBUSY 신호가 해제될 때 까지는 버스 마스터들이 요구 신호를 보낼 수 없거나,
    2. 버스 요구 신호는 항상 발생시킬 수 있지만, 승인 신호를 받은 후에도 BBUSY 신호가 해제될 때까지는 버스를 사용할 수 없도록 함

2) 분산식 직렬 중재 방식

  • 데이지-체인 버스 승인 신호(DBGNT) 선이 버스 중재기들을 순환형으로 접속한 형태로 구성
  • 버스 사용권을 부여받은 마스터가 버스 사용을 시작하는 순간에 DBGNT 신호를 세트하여 자신의 바로 우측 마스터의 중재기로 보내줌
  • 신호를 받은 중재기의 마스터가 버스 사용을 신청한 상태였으면 BGNT 신호를 발생시켜 마스터에게 보내며, 버스 요구를 하지 않은 상태라면 다음 중재기로 넘겨줌
  • 각 마스터의 우선순위가 계속 변한다는 특징
    • 즉, 어떤 마스터가 버스 사용 승인을 받으면 그 마스터는 다음 중재 동작에서는 최하위 우선순위를 가지게 되고, 그 마스터의 바로 우측에 위치한 마스터가 최상위 우선순위를 가지게 됨
  • 이 방식은 분산식이지만, 어떤 한 중재기에 결함이 발생하면 DBGNT 신호를 통과시킬 수 없기에 전체 동작이 중단될 수 있음

2-3. 폴링 방식

버스 중재기가 각 마스터들이 버스 사용을 원하는지를 주기적으로 검사하여 버스 승인 여부를 결정

1) 하드웨어 폴링 방식
  • 중재기 내의 고정된 하드웨어를 이용한 주기적 검사를 통해 중재 기능을 수행하는 방식
  • N개의 마스터를 가진 시스템에는 N개의 폴링 선 혹은 2진 코드화된 폴링 주소를 사용할 시 logN개의 폴링 선 필요
  • 각 마스터들의 우선순위는 중재기가 마스터를 검사하는 순서에 의해 결정됨

2) 소프트웨어 폴링 방식
  • 하드웨어 폴링 방식과 동일하게 구성
  • 그러나 중재 동작이 고정된 하드웨어에 의해 이루어지는 것이 아니라 프로세서의 의해 조정되는 방식
    • 하드웨어 방식에 비하여 속도가 더 느리지만, 융통성이 높다는 장점
  • 중재 프로세서는 다음에 폴링할 마스터의 주소를 기억할 수 있고, 필요에 따라 폴링 순서를 변경할 수도 있음
  • 만약 어떤 버스 마스터에 결함이 발생한다면, 그 마스터를 폴링 순서에서 제외하여 시스템 결함 허용도를 높일 수 있음

3. 인터럽트를 이용한 I/O

프로그램을 이용한 I/O 방식은 CPU가 I/O 동작에 계속 관여해야 하기 때문에 CPU 시간의 낭비를 초래

  • 인터럽트 메커니즘을 이용하여 CPU와 I/O 장치간 상호작용을 처리하는 인터럽트-구동 I/O 방식을 통해 해결 가능
    1. CPU가 I/O 제어기에 명령을 보냄. 그 후, CPU는 다른 작업 수행
    2. 제어기는 I/O 명령을 이용하여 I/O 장치를 제어
    3. I/O 장치가 명령 수행을 완료하면, 제어기는 CPU로 인터럽트 신호를 보냄
    4. CPU는 인터럽트 신호를 받는 즉시 원래의 프로그램으로 돌아와서 다음 작업을 계속함

3-1. 다중 인터럽트 방식

  • 각 I/O 제어기와 CPU 상에 별도의 인터럽트 요구 신호선과 인터럽트 확인 신호선이 한 개씩 존재
  • 두 개 이상의 I/O 장치들이 동시에 인터럽트 요구 신호를 보내는 경우, 각 I/O 장치에 대하여 우선순위를 정하고, 더 높은 우선순위를 가진 장치의 인터럽트 요구부터 확인 신호를 보내고 서비스
  • 각 I/O 장치가 별도의 인터럽트 선을 가지고 있기 때문에 CPU가 인터럽트를 요구한 장치를 쉽게 찾아낼 수 있다는 장점

3-2. 데이지-체인 방식

  • 모든 I/O 제어기들은 한 개의 인터럽트 요구 신호 선을 공유
  • 인터럽트 확인 신호는 첫 번째 I/O 제어기로 보내짐
    • 만약 그 제어기가 인터럽트를 요구한 상태라면, 즉시 데이터 버스를 통해 자신의 ID(인터럽트 벡터)를 CPU에 보냄
    • 인터럽트를 요구한 상태가 아니라면, 그 제어기는 확인 신호를 다음 제어기로 통과시킴
  • 간단한 하드웨어 구성이지만, 많은 수의 I/O 장치들이 접속된 시스템에서는 기근 현상 초래할 가능성 있음

3-3. 소프트웨어 폴링 방식

  • 한 개의 TEST I/O 선이 CPU와 모든 제어기들 사이에 연결
  • TEST I/O 신호는 각 제어지 내 인터럽트 플래그의 상태를 확인
  • 데이지-체인 방식과 같은 방식으로 작동하지만 소프트웨어가 그 역할을 수행하는 것이 차이

4. DMA를 이용한 I/O

인터럽트-구동 I/O 방식 역시 기억장치와 I/O 장치간 데이터 이동에 CPU가 직접 개입해야 하며, 이동되는 데이터들이 반드시 CPU를 경유해야 하는 문제 존재

  • DMA(Direct Memory Access)란 CPU의 개입 없이 I/O 장치와 기억장치 사이에 데이터 전송을 수행하는 메커니즘
  • CPU는 DMA 제어기로 다음과 같은 정보들이 포함된 명령어를 보냄
    • I/O 장치의 주소
    • 연산 지정자
    • 데이터가 읽혀지거나 쓰여질 주기억장치 영역의 시작 주소
    • 전송될 데이터 단어들의 개수
  • CPU는 I/O 동작을 DMA 제어기에게 맡기고, 모든 데이터 전송 동작이 완료될 때까지 전혀 개입하지 않음
  • CPU와 DMA는 시스템 버스를 공유: DMA가 시스템 버스를 통해 주기억장치에 접근하기 때문
    • DMA 제어기는 가능한 한 CPU의 정상적 동작을 방해하지 않으며 시스템 버스를 사용
    • 이로 인해 DMA를 사이클 스틸링이라고도 부름
    • 그러나 큰 데이터 블록 전송의 경우 스틸한 사이클만 사용하기에는 턱없이 부족
      • 일반적으로 DMA 제어기가 시스템 버스 사용(BUS REQ)을 요구하면, CPU는 현재 사이클이 끝나는 즉시 사용 허가(BUS GRANT)를 해줌

  • DMA 제어기의 내부 구조
    • I/O 장치의 주소를 저장하는 '주소 레지스터'
    • 데이터 버퍼 역할을 하는 '데이터 레지스터'
    • 전솔될 데이터 수를 저장하는 '카운터 레지스터'
    • 각종 제어 신호들을 발생하거나 받아들이기 위한 '제어 회로'
  • 시스템 버스에 주기억장치, I/O 제어기가 모두 달려있는 경우, 데이터 x 2번만큼 시스템 버스를 사용 -> 비효율
  • DMA 제어기 아래에 I/O 제어기를 두자 !
    • 시스템 버스는 데이터 x 1번 만큼만 사용하면 됨
    • 그러나, 각 DMA 제어기에 직접 접속할 수 있는 I/O 제어기의 수가 제한되어 여러 DMA 제어기들을 두어야 할 수도 있음
  • DMA 제어기를 시스템 버스와 I/O 버스 사이에 위치시키고, I/O버스를 통해 I/O 장치를 제어하자!
    • I/O 장치들은 종류와 속도가 다양하고 제어 방법도 복잡하기 때문에, 간단한 구조를 가진 DMA제어기로 지원하는 데 한계 존재
    • 디스크 쓰기/읽기의 경우 데이터 블록의 크기가 512byte 이상이기 때문에 그 데이터들을 임시 저장하기 위한 내부 기억 장치 필요

  • I/O 장치들의 동작을 제어하며 DMA 동작도 제어하는 프로세서인 I/O 프로세서(IOP)의 사용

    • I/O 제어 프로그램을 수행할 수 있는 프로세서
    • 데이터 블록을 임시 저장할 수 있는 용량의 지역 기억장치
    • 시스템 버스 인터페이스 및 버스 마스터 회로
    • I/O 버스 인터페이스 및 중재 회로로 구성


'Software Convergence > Computer Architecture' 카테고리의 다른 글

폰 노이만 아키텍쳐  (0) 2018.09.09
기억장치  (0) 2018.09.08
CPU의 구조와 기능  (0) 2018.09.08

쉘 학습

쉘 이란 무엇인가?

  • 쉘이란 키보드로 입력한 명령어를 운영체제에 전달하여 이 명령어를 실행하게 하는 프로그램
  • 대부분의 리눅스 배포판은 bash라고 하는 GNU 프로젝트의 쉘 프로그램 제공

터미널 에뮬레이터

  • GUI 환경에서는 쉘과 직접 작업할 수 있도록 도와주는 터미널 에뮬레이터라는 프로그램이 필요
  • 다양한 터미넬 에뮬레이터가 존재하지만 모두 기본적으로 쉘에 접근할 수 있게 해준다는 같은 기능을 수행

첫 번째 키 입력

  • 쉘 프롬프트: 쉘이 입력 가능한 상태일 때 나타남
    • 보통 username@machinename 과 같은 형식을 포함
    • 뒤이어 현재 작업 디렉토리와 $ 표시가 옴
  • 프롬프트의 마지막 글자가 # 이면 현재 터미널 세션이 super user 권한을 가졌다는 뜻

명령어 히스토리
  • 위쪽 방향키 눌러서 명령어 히스토리 기능 사용 가능
  • 기본적으로 가장 최근 500개의 명령어 기억 가능

간단한 명령어 실행하기

  • date: 현재 시간과 날짜 표시
  • cal: 현재 날짜의 달력을 표시
  • df: 현재 사용 중인 디스크 정보와 사용 가능한 디스크의 용량 표시
  • free: 메모리 사용 현황 정보 표시
  • exit: 터미널 세션 종료

파일시스템 탐색

파일시스템 트리 구조의 이해

  • 리눅스와 같은 유닉스형 운영체제는 계층적인 디렉토리 구조로 파일을 구성
  • 파일시스템의 최상위 디렉토리를 루트 디렉토리라고 부름
  • 윈도우즈는 저장장치마다 개별 파일시스템으로 관리하는 반면 유닉스형 시스템에서는 단일 파일시스템으로 관리
    • 유닉스형 시스템의 저장장치들은 시스템 유지보수를 담당하는 관리자의 재량에 따라 다양한 위치에 마운트

현재 작업 디렉토리

  • 현재 사용자가 위치해있는 지점을 현재 작업 디렉토리 라고 함
  • 표시를 위한 명령어는 pwd(Print Working Directory)

  • 시스템에 처음 로그인하면 홈 디렉토리가 현재 작업 디렉토리가 됨
    • 사용자 계정마다 고유의 홈 디렉토리가 있으며, 일반 사용자로 시스템을 사용할 때 파일 쓰기 권한이 부여된 유일한 공간

디렉토리 목록 표시

  • 현재 작업 디렉토리에 있는 파일과 하위 디렉토리를 표시할 때는 ls 명령어 사용

현재 작업 디렉토리 변경

  • cd 명령어로 Working Directory의 변경 가능

절대 경로명
  • 루트 디렉토리에서 원하는 디렉토리 또는 파일까지의 경로
    • ex) /usr/bin

상대 경로명
  • 현재 작업 디렉토리가 시작점이 됨
    • . 기호는 현재 작업 디렉토리를 나타냄
    • .. 기호는 작업 디렉토리의 상위 디렉토리를 의미
    • 거의 모든 경우에 ./ 기호는 생략 가능
  • 유용한 단축 표현들
    • cd: 작업 디렉토리를 홈 디렉토리로 변경
    • cd -: 작업 디렉토리를 이전 작업 디렉토리로 변경
    • cd ~ username: username의 홈 디렉토리로 작업 디렉토리를 변경

시스템 살펴보기

재미있는 ls 명령어

  • ls /usr: 다른 디렉토리의 목록 보기
  • ls ~ /usr: 한 번에 여러 디렉토리 목록 보기
  • ls -l: 파일 및 디렉토리 이름 뿐 아니라 더 자세한 속성까지 확인하기 위한 -l 옵션

명령어 옵션과 명령 인자
  • 명령어는 주로 하나 이상의 옵션명령 인자들과 함께 사용
  • 주로 많이 사용되는 ls 옵션
    • -a: 모든 파일 보기(숨김 파일까지도 표시)
    • -d: -l 옵션과 함께 사용하여 디렉토리 자체 정보를 확인
    • -F: 지시 문자를 추가로 표시(디렉토리 명이면 끝에 / 덧붙임)
    • -h: -l 옵션과 함께 사용하여 파일 크기를 인식하기 쉬운 형태로 표시
    • -l: 자세한 정보 출력
    • -r: 출력 결과를 역순으로 표시
    • -s: 파일 크기순으로 정렬
    • -t: 파일 수정 시간순으로 정렬

file 명령어로 파일 타입 확인

  • 파일명이 직관적이지 않은 리눅스에서는 file 명령어를 통해 파일에 대한 정보 확인 가능

less 명령어로 파일 정보 보기

  • less 명령어는 텍스트 파일을 볼 때 사용하는 프로그램
  • 환경설정 파일, 스크립트와 같은 것들이 텍스트 형식으로 저장됨
    • less 프로그램은 그러한 텍스트들을 확인할 때 매우 편리한 방식 제공
  • less is more: 긴 텍스트도 페이지별로 쉽게 볼 수 있도록 지원해주는 pagers의 일종인 less
  • less 명령키
    • b: 한 페이지 위로
    • space: 한 페이지 아래로
    • 위쪽 방향키: 한 줄 위로
    • 아래쪽 방향키: 한 줄 아래로
    • G: 텍스트 파일 맨 마지막으로
    • g: 텍스트 파일 맨 처음으로
    • /문자열: 아래 방향으로 진행하며 입력된 문자열 찾기
    • n: 이전 검색어의 다음 찾기
    • h: 도움말 보기
    • q: 프로그램 종료

심볼릭 링크

  • 대부분의 유닉스형 시스템에서는 하나의 파일에 여러 이름을 부여할 수 있음
  • Why Symbolic Link?
    • foo 파일의 현재 버전이 2.6이고, foo-2.6이라는 파일명으로 저장되어 있음
    • 심볼릭 링크를 하나 생성해서 이것이 foo-2.6 파일을 가리키도록 함
    • 프로그램이 foo 파일을 사용하려고 할 때 실제 foo-2.6 파일을 열 수 있도록 하는 것
    • 만약 foo 파일이 2.7 버전으로 업그레이드 되면?
      • 이전 심볼릭 링크를 삭제하고 foo-2.7 파일을 가리키도록 다시 심볼릭 링크를 지정해주면 해결되는 문제 !

파일과 디렉토리 조작

와일드카드

  • 파일명을 그룹 지정할 수 있도록 지원해주는 특수 문자
  • 와일드 카드
    • *: 모든 문자
    • ?: 모든 하나의 문자
    • [characters]: characters 문자셋에 포함된 문자
    • [!characters]: characters 문자셋에 포함되지 않은 문자
    • [[:class:]]: 지정된 문자 클래스에 포함된 문자
  • 가장 많이 사용되는 문자 클래스
    • [:alnum:]: 모든 알파벳과 숫자 문자
    • [:alpha:]: 모든 알파벳 문자
    • [:digit:]: 모든 숫자
    • [:lower:]: 모든 소문자
    • [:upper:]: 모든 대문자
  • 와일드 카드 사용 예시
    • *: 모든 파일
    • g*: g로 시작하는 모든 파일
    • b*.txt: b로 시작하되 .txt 형식 파일
    • Data???: Data로 시작하면서 뒤에 세 개의 문자만 있는 파일
    • [abc]*: a, b, c로 시작하는 모든 파일
    • BACKUP.[0-9][0-9][0-9]: BACKUP으로 시작하면서 뒤에 세 개의 숫자로 된 파일
    • [[:upper:]]*: 대문자로 시작하는 모든 파일
    • [![:digit:]]*: 숫자로 시작하는 모든 파일
    • *[[:lower:]123]: 파일명이 소문자로 끝나거나 1, 2, 3으로 끝나는 파일

디렉토리 생성

mkdir directory_name
mkdir dir1 dir2 dir3

파일 및 디렉토리 복사

cp item1 item2
# item2라는 파일이 이미 있다면 item1 내용을 그대로 덮어쓰게 되며, 없으면 새로 생성
  • cp 옵션
    • -a: 파일 및 디렉토리뿐만 아니라 소유자 및 권한 정보 등 속성까지 모두 복사
    • -i: 기존 파일을 덮어쓰기 전에 확인 메시지를 보여주는 옵션
    • -r: 디렉토리와 그 안의 내용까지 복사하는 옵션
    • -u: 디렉토리에 없거나 최신 버전인 파일만 복사하기 위한 옵션
    • -v: 복사가 완료되었는지 메시지 보여줌

파일 이동과 이름 변경

  • mv 명령어 사용하여 파일을 이동하거나 파일명 수정 가능
  • mv 옵션
    • -i: 기존 파일을 덮어쓰기 전에 확인 메시지를 보여주는 옵션
    • -u: 디렉토리에 없거나 최신 버전인 파일만 이동하기 위한 옵션
    • -v: 복사가 완료되었는지 메시지 보여줌

파일 및 디렉토리 삭제

  • 리눅스는 삭제를 취소할 수 있는 명렁어가 없음
  • 특히 와일드카드와 함께 rm 명령어를 사용할 때는 주의를 요해야 함!
  • rm 옵션
    • -i: 기존 파일을 삭제하기 전에 확인 메시지를 보여주는 옵션
    • -r: 디렉토리를 완전히 삭제하기 위한 옵션
    • -f: 존재하지 않는 파일은 확인 메시지 없이 무시하라는 옵션
    • -v: 삭제가 완료되었다는 메시지를 보여주는 옵션

링크 생성

하드 링크
  • 하드 링크는 링크를 생성하는 기존 유닉스 방식
ln item link
  • 기본적으로 하나의 파일에는 하나의 하드 링크가 있으며, 이것이 파일에 이름을 만들어주는 것
  • 하드 링크의 약점
    • 파일 시스템 외부에 있는 파일을 참조할 수 없음. 즉 같은 디스크 파티션 파일만 참조 가능
    • 디렉토리 참조 불가
  • 하드 링크를 포함한 디렉토리 목록은 해당 링크가 가리키고 있는 것이 무엇인지 보여주지 않음
  • 하드 링크가 삭제될 때, 링크도 함께 사라지지만 파일 내용은 그 파일의 모든 링크가 삭제될 때 까지 계속 남아있음
  • 하드 링크 파일은 같은 inode를 공유하며, 동일한 파일을 가리키게 됨

심볼릭 링크
  • 하드 링크의 한계를 극복하기 위해 탄생
ln -s file line
  • 참조될 파일이나 디렉토리를 가리키는 텍스트 포인터가 포함된 특수한 파일을 생성
  • 포인터와 비슷한 개념
    • 심볼릭 링크에 편집을 하게 되면 심볼릭 링크가 참조하고 있는 파일도 역시 똑같은 변경이 이루어짐
      • 하지만 심볼릭 링크를 삭제하는 경우엔 그 링크만 삭제되고 파일은 남아있음
    • 심볼릭 링크를 삭제하기 전에 파일을 지우면 링크는 살아있지만 이 링크는 아무 것도 가리키지 않음
  • 심볼릭 링크를 포함하고 있는 디렉토리가 링크를 유지하면서 파일명을 변경하거나 파일을 이동할 수 있도록 허용하기 때문에 상대 경로를 사용하는 것이 바람직함

명령어와 친해지기

명령어란?

  • 명령어는 다음 네 가지 중 하나
    1. 실행 프로그램
    2. 쉘에 내장되어 있는 명령어: shell builtins이라고 하는 다수의 명령어를 내부적으로 지원
    3. 쉘 함수
    4. 별칭: 다른 명령어로부터 우리만의 명령어를 새롭게 정의 가능

명령어 확인

명령어 타입 표시
  • type 명령어는 쉘에 내장된 형식으로 명령어 이름을 입력하면 쉘이 실행하게 될 명령어의 타입을 보여줌
type command
ex) type ls

실행 파일의 위치 표시
  • 실행할 프로그램의 정확한 위치를 파악하기 위해 which 명령어 사용
which ls
-> /bin/ls
  • which 명령어는 오직 실행 프로그램만을 대상으로 함
    • 어떤 builtin이나 별칭에는 동작하지 않음

명령어 도움말 보기

쉘 빌트인 도움말 보기

  • help를 쉘 내장 명령어 이름 앞에 입력하여 도움말 기능 사용 가능
  • 부가적 옵션과 인자 옵션이 무엇인지 알려주는 기능

사용법 표시

  • 명령어 문법과 옵션에 대한 설명을 보여주는 --help 옵션

프로그램 매뉴얼 페이지 표시
  • 커맨드 라인용 실행 프로그램 대부분은 man 페이지라고 불리는 공식적인 프로그램 설명서를 제공
man program
ex) man ls
  • 일반적으로 제목, 명령어 문법 개요, 명렁어 사용 목적, 그리고 명령어 옵션에 대한 설명 정도가 들어있음
  • man 페이지를 표시하는 동안 모든 less 명령의 사용이 가능
  • -k 옵션과 함께 사용하여 검색어에 따라 일치하는 명령어의 man 페이지 목록을 검색 가능
man -k floppy
# floppy라는 내용이 들어간 man 페이지의 이름을 찾아줌

간략한 명령어 정보 표시

  • 특정 키워드에 부합하는 man 페이지에 대하여 그 이름과 한 줄의 간략한 정보를 보여줌

별칭으로 나만의 명령어 만들기

  • 세미콜론으로 각 명령어를 구분하여 한 줄에 하나 이상의 명령어를 입력할 수 있음
  • alias name='string' 형태로 별칭 명령어 생성 가능




'Software Convergence > OS, Linux ' 카테고리의 다른 글

Linux Command Line Guide(2)  (0) 2018.09.10
vimtutor 내용 정리  (0) 2018.09.09
sudo 명령어 수행 안되는 경우  (0) 2018.07.25
Daemon이란?  (0) 2018.07.19
ssh로 서버 간 password 없이 접속하기  (0) 2018.07.17

상속의 단점과 Strategy Pattern

추상 클래스와 인터페이스

추상 클래스

  • 추상 클래스는 클래스의 일종
  • 상속 받은 정보를 부모 클래스로부터 Look-up 하여 살펴봐야 하기 때문에 인터페이스 보다 비싼 연산
  • 정수 / 멤버 / 정의되지 않은 메소 / 정의된 메소드를 지닐 수 있음
  • 메소드와 멤버를 어느 접근제한자로든 설정할 수 있음
  • 추상 클래스를 상속 받는 서브 클래스는 반드시 상속 받은 abstract method를 정의해주어야 함
    • cf) 추상 클래스 역시 다른 추상 클래스를 상속 받을 수 있는데 이 때는 부모 추상 클래스의 abstract method를 반드시 정의해줄 필요는 없음
  • 서브 클래스는 한 번에 한 개의 추상 클래스만을 상속 받을 수 있음
  • abstract method를 정의할 때 자식 클래스는 해당 method의 접근 제한자 level을 같거나 혹은 더 낮게 재설정할 수 있음
abstract class MotorVehicle
{
    int fuel;

    int getFuel()
    {
        return this.fuel;
    }

    abstract void run();
}

class Car extends MotorVehicle
{
    void run()
    {
        print("Wrroooooooom");
    }
}


인터페이스

  • 인터페이스는 일종의 계약이자 빈 껍데기. method들의 코드가 아닌 몸체만 존재하는 패턴과 같은 것
  • 인터페이스는 단순히 이름들의 나열이기 때문에 아주 적은 CPU만을 소모. 따라서 Look-up 작업을 할 필요가 없음
  • 정수 / 정의되지 않은 메소드를 지닐 수 있음
  • 모든 메소드들은 public level로 정의되어야 함
  • 인터페이스는 부모 인터페이스를 extend할 수 있는데 이 때 부모 인터페이스의 method를 구현할 필요 없음
    • 인터페이스는 method 구현이 불가하기 때문
  • 서브 클래스는 한 번에 여러 개의 인터페이스를 사용할 수 있음
  • 인터페이스를 사용하는 서브 클래스는 반드시 method를 같은 접근 제한 레벨인 public으로 정의해주어야 함
interface MotorVehicle
{
    void run();

    int getFuel();
}

class Car implements MotorVehicle
{
    int fuel;

    void run()
    {
        print("Wrroooooooom");
    }

    int getFuel()
    {
        return this.fuel;
    }
}



상속의 단점

  • 상위 클래스 기능에 버그가 생기거나 기능의 추가/변경 등으로 변화가 생겼을 때 상위 클래스를 상속 받는 하위 클래스가 정상적으로 작동할 수 있을지에 대한 예측이 힘듬
    • 하위 클래스는 상위 클래스의 부분 집합이기 때문에
  • 상속 구조가 복잡해질 수록 그 영향에 대한 예측이 힘들어짐
  • 상위 클래스에서 의미 있었던 기능이 하위 클래스에서는 의미 없는 기능일 수 있음
  • 하위 클래스는 반드시 상위 클래스로부터 물려 받은 기능들을 제공해야 함 + 하위 클래스에서 기능들이 추가됨
    • 기능 확장에 따라 상위 클래스에서 파생된 클래스들이 많아지고, 그 규모가 커짐에 따라 일관성 있게 작성하지 않은 클래스들에 대한 이해도는 점차 복잡해지고 사용에 어려움이 생길 수 있음

Strategy Pattern

  • 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만듦

  • 일반적으로 서브 클래스를 만드는 방법을 대신하여 유연성을 극대화시키는 용도로 사용

  • Strategy Pattern에서 class의 행위와 그 알고리즘은 run time시 변경될 수 있음

  • Strategy pattern에서 우리는 다양한 strategy를 나타내는 객체들과 수행하는 행위가 strategy에 따라 달라지는 context 객체를 생성

  • Strategy 객체는 context 객체의 수행 알고리즘을 변경하는 역할 수행

  • 인터페이스 생성

public interface Strategy {
    public int doOperation(int num1, int num2);
}
  • 같은 인터페이스를 사용하는 concrete class들을 생성 (concrete class: 지니고 있는 method들이 모두 정의되어 있는 class)
public class OperationAdd implements Strategy{
    @Override
    public int doOperation(int num1, int num2){
        return num1 + num2;
    }
}

public class OperationSubstract implements Strategy{
    @Override
    public int doOperation(int num1, int num2){
        return num1 - num2;
    }
}

public class OperationMultiply implements Strategy{
    @Override
    public int doOperation(int num1, int num2){
        return num1 * num2;
    }
}
  • Context 클래스 생성
public class Context{
    private Strategy strategy;

    public Context(Strategy strategy){
        this.strategy = startegy;
    }

    public int executeStrategy(int num1, int num2){
        return strategy.doOperation(num1, num2);
    }
}
  • Strategy가 바뀔 때 마다의 변화를 보기 위해 main 함수에서 Context 클래스 사용해봄
public class StrategyPatternDemo{
    public static void main(String[] args){
        Context context = new Context(new OperationAdd());
        System.out.println("10 + 5 = " + context.executeStartegy(10, 5)); // 15

        context = new Context(new OperationSubstract());
        System.out.println("10 - 5 = " + context.executeStartegy(10, 5)); // 5

        context = new Context(new OperationMultiply());
        System.out.println("10 * 5 = " + context.executeStartegy(10, 5)); // 50
    }
}


'Software Convergence > Java' 카테고리의 다른 글

다형성: Polymorphism  (0) 2018.11.05
static과 접근제한자  (0) 2018.10.19
Generic 자료형 실습  (0) 2018.07.12
instanceof / encapsulation  (0) 2018.07.04
다형성(Polymorphism) / Object 클래스  (0) 2018.07.03

지옥에서 온 Git(3)

branch 만들기

  • branch는 작업을 분기해서 처리하기 위한 명령어
  • 브랜치의 목록을 볼 때
git branch
  • 브랜치를 생성할 때
git branch "새로운 브랜치 이름"

# 새로운 브랜치를 생성하면 생성 시기에 속해 있던 브랜치의 상태를 그대로 복사해서 가져감
  • 브랜치를 전환 할 때
git checkout "전환하고자 하는 브랜치 이름"
  • 브랜치를 삭제할 때
git branch -d "삭제하고자 하는 브랜치 이름"

# 병합되지 않은 브랜치를 강제 삭제할 때
git branch -D "삭제하고자 하는 브랜치 이름"
  • 브랜치를 생성하고 바로 전환하고자 할 때
git checkout -b "생성 및 전환할 브랜치 이름"

branch 정보 확인

  • 브랜치 간에 비교할 때
git log "비교할 브랜치 A".."비교할 브랜치 B"

# 브랜치 A에는 없고 브랜치 B에는 있는 것들을 표기 
  • 로그에 모든 브랜치와 브랜치 명을 표시
git log --all
or 
git log --branches

# --decorate는 기본 옵션으로, 브랜치 명을 표시하지 않기 위해서는 --no-decorate 옵션을 붙여주어야 함
  • 로그에 나타는 모든 브랜치들을 그래프화 하고자 할 때
git log --all --graph

# --oneline 옵션을 추가해주어 더 간략하게 볼 수도 있음

  • 브랜치 간의 현재 상태를 비교할 때
git diff "비교할 브랜치 A".."비교할 브랜치 B"



branch 병합

  • A 브랜치로 B 브랜치를 병합할 때 (B -> A)
git checkout A
git merge B


  • merge 이후에는 -d 옵션으로 브랜치의 삭제가 가능
    • 이미 모든 수정 정보를 얻었기 때문


branch와 merge

  1. Fast-Forward: 별도의 commit을 생성하지 않고, master가 가리키는 commit을 바꾸는 방식
  2. Recursive startegy: 같은 조상에서 분기된 브랜치 A가 조상 commit으로 부터 변화가 생겼으면 'Fast-Forward'방식을 사용할 수 없음. 따라서 별도의 commit을 생성하여 merge하게 됨



    • cf. A가 가리키는 commit + B가 가리키는 commit + 공통의 조상 commit. 총 3개의 commit이 3-way Merge를 수행




branch 충돌 해결

  • 같은 파일임에도 수정한 위치가 다르다면 자동으로 합쳐버림
    • 즉, 브랜치 A에서 소스코드 상단을. 브랜치 B에서 하단을 수정하여 Merge하게 되면 Auto-Merging이 가능
  • 그러나, 수정한 위치가 같으면 충돌이 발생

  • 충돌난 소스코드 부분을 사용자가 직접 수정하여 충돌의 해결이 가능



stash

  • branch를 이용하여 작업을 하던 중 현재 branch에서 작업이 끝나지 않았는데 다른 branch로 checkout을 해야 하는 경우에 사용
    • commit을 해주지 않고 checkout을 할 경우 branch에서의 작업이 이동한 branch에 까지 영향을 미침. 그러나, 아직 작업 중인 것을 commit 하기에는 애매하기 때문
  • git stash의 적용

  • stash 해두었던 변경 사항 불러오기
git stash apply
  • stash list에 있는 stash 기록은 사용자가 명시적으로 삭제하지 않는 한 계속 존재함
    • 즉, git reset --hard를 통해 stash가 적용된 상황으로 돌아가더라도 stash 해두었던 기록은 계속 남아있음
    • git stash list를 통해 stash 한 기록을 볼 수 있음
  • git stash apply는 stash list 중 항상 가장 위에 있는 stash를 불러옴
    • 따라서, stash를 불러온 후 다음 stash를 불러오기 위해서는 이미 불러온 stash를 삭제해 주어야 함
git stash apply; git stash drop
or
git stash pop 
  • stash는 최소한 버전 관리가 되고 있는 파일에 대해서만 적용이 가능함
    • 즉, add 되지 않은 파일에 대해서는 stash의 적용이 불가


'Software Convergence > Git' 카테고리의 다른 글

지옥에서 온 Git (2)  (0) 2018.08.24
지옥에서 온 Git (1)  (1) 2018.08.22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class hashTable:
    def __init__(self):
        self.size = 10
        self.table = [[] for _ in range(self.size)]
 
    def hashfunc(self, key, size):
        return key % size
 
    def insert(self, key, value):
        position = self.hashfunc(key, self.size)
        key_exists = False
        bucket = self.table[position]
        # 동일 key 값 사용 여부 검사
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                key_exists = True
                break
        # key 값 존재하는 경우 value replace
        if key_exists:
            bucket[i] = ((key, value))
        # 존재하지 않는 경우 key-value 쌍 추가
        else:
            bucket.append((key, value))
 
    def search(self, key):
        position = self.hashfunc(key, self.size)
        bucket = self.table[position]
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                return v
 
    def delete(self, key):
        position = self.hashfunc(key, self.size)
        key_exists = False
        bucket = self.table[position]
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                key_exists = True
                break
        if key_exists:
            del bucket[i]
            print('Key {} deleted'.format(key))
        else:
            print('Key {} not found'.format(key))
cs


underscore

  • index가 필요없는 for문을 작성할 경우에 사용 가능
for _ in range(10):
    print("hello")

enumerate

  • 순서가 있는 자료형(리스트, 튜플, 문자열)을 입력으로 받아 index 값을 포함하는 enumerate 객체를 리턴
for i, name in enumerate(['body', 'foo', 'bar']):
    print(i, name)

0 body
1 foo
2 bar
  • enumerate를 for문과 함께 사용하면 자료형의 현재 순서와 그 값을 쉽게 알 수 있음
  • for문처럼 반복되는 구간에서 객체가 현재 어느 위치에 있는지 알려주는 index 값이 필요할 때 enumerate 함수를 사용하면 매우 유용

Tuple의 packing과 unpacking

  1. Packing: Tuple에 여러 개의 값을 넣는 것
>>> a = 5
>>> b = 'test'
>>> c = a, b
>>> print(c) 
(5, 'test')
  1. Unpacking: 패킹된 Tuple에서 여러 개의 값을 꺼내오는 것
>>> a = (63, 'building')
>>> b, c = a
>>> b
63
>>> c
'building'


Hash Table(해쉬 테이블)

Introduction

  • 테이블에 저장되는 데이터는 Key와 Value가 한 쌍을 이룸
  • Key가 존재하는 Value는 저장할 수 없으며, 모든 key는 중복되지 않음
  • 좋은 해쉬 함수는 '충돌을 덜 일으키는 해쉬 함수'
  • 일반적인 해쉬 함수(remainder method)의 개선안
    1. 자릿수 폴딩(Folding Method): 자릿수를 균등한 조각으로 나누어 각각의 값을 더한 후, 그 값의 remainder를 key값으로 설정
      • ex) 436-555-4601이라는 값을 두 자리로 쪼깨어 더함
      • 43 + 65 + 55 + 46 + 01 = 210
      • 210 % 11 = 1으로 1의 key값을 가지게 됨
      • 각각의 조각을 reverse하여 더하는 folding method도 존재
        • ex) 43 + 56 + 55 + 64 + 01 = 219
    2. 중간 제곱법(Mid-Square Method): Value를 제곱한 후 가운데 값의 remainder를 key값으로 설정
      • ex) 44 ^ 2 = 1,936
      • 93 % 11 = 5로 5의 key값을 가지게 됨
    3. Ordinal Value의 활용: 문자열을 구성하는 각각의 문자의 ASCII 값을 모두 더해 나온 결과의 remainder를 key 값으로 설정하는 방법
      • ex) 'cat' = 99 + 97 + 116 = 312
      • 312 % 11 = 4로 4의 key값을 지니게 됨
      • 그러나 이는 anagram의 단어들이 모두 같은 값을 지니게 된다는 단점이 있음
        • 해결 방안: weighting factor의 추가
        • 'cat' = 99*1 + 97*2 + 116*3
  • 충돌 문제의 해결안
    • Open Addressing: 해쉬 테이블에서 다음 open된 slot을 찾는 방법

      1. 선형 조사법(Linear Probing): hash function에 의해 저장되어야 하는 slot의 다음 slot들을 차례로 확인하며 빈 slot을 찾아가는 방법
        • ex) 77, 44, 55는 모두 0번 index에 들어가야 하는 value들
        • 그러나 77이 0번 index를 차지하고 있기 때문에 44는 1번 index에, 55는 0, 1번 index가 꽉찬 것을 확인하고 비어있는 2번 index에 저장하는 방법
        • 선형 조사법의 단점은 Clustering이 생긴다는 것
          • 즉, slot들을 골고루 사용하는 것이 아닌 특정 지역의 slot들만 과도하게 사용되는 경향이 생김
      2. 이차 조사법(Quadratic Probing): hash function에 의해 저장되어야 하는 slot 위치에 + 1, + 4, + 9 등을 더해 slot들이 고르게 분배되도록 하는 방법
        • ex) 77, 44, 55의 value들을 저장해야 함
        • 77은 0번 index에 44는 0 + 1으로 1번 index에 그리고 55는 0 + 4로 4번 index에 저장시키게 됨
    • Close Addressing(Chaining): 각각의 slot에 item들이 여러 개 저장될 수 있도록 구현하는 방법




해쉬 테이블 구현에 필요한 기본 연산

  • HashTable(): 새로운 HashTable을 생성
  • put(key, val): key와 value을 쌍으로 HashTable에 추가함. Key 값이 이미 존재하면 old value를 new value로 변경
  • get(key): key가 주어지면 그에 맞는 value값을 반환
  • del: Key 값을 이용하여 Key-Value 쌍을 삭제
  • len(): Key-Value 쌍의 크기 반환
  • in: HashTable에 Key 값이 있는지의 여부를 반환

해쉬 테이블의 로직

class HashTable:
	def __init__(self):
		self.size = 11
		self.slots = [None] * self.size
		self.data = [None] * self.size
  • Key 값들을 담는 slots라는 list 생성
  • slots라는 list와 평행하게 존재하며, value를 담을 data list 생성
  • HashTable의 크기는 소수로 설정하는 것이 충돌 문제 해결에 효과적

def put(self, key, value):
	hashval = self.hashfunction(key, len(self.slots))

	if self.slots[hashvalue] == None:
		self.slot[hashvalue] = key
		self.data[hashvalue] = data

	else:
		# original key값이 같을 경우 replace
		if self.slots[hashvalue] == key:
			self.data[hashvalue] = data
		else:
			nextslot = self.rehash(hashvalue, len(self.slots))
			# slot이 차있고, original key값과 rehash 값이 다르면 계속 rehash
			while self.slots[nextslot] != None and \
							self.slots[nextslot] != key:
				nextslot = self.rehash(nextslot, len(self.slots))
		
			# slot이 비어있으면 slot에 key와 data 입력
			if self.slots[nextslot] == None:
				self.slots[nextslot] = key
				self.data[nextslot] = data

			# rehash 값과 original key 값이 같을 경우 replace
			# 즉, rehash 통해 이미 자리한 경우
			else:
				self.data[nextslot] = data

def hashfunction(self, key, size):
	return key % size

# Linear Probing 사용
def rehash(self, oldhash, size):
	return (oldhash+1) % size
  • nextslot의 rehash를 탈출하는 조건은 2가지로 분류
    1. rehash를 통해 얻은 slot이 비어있는 경우: 빈 자리에 key와 data를 삽입
    2. rehash를 통해 얻은 slot의 key값과 입력 값의 key 값이 같은 경우: value를 replace
  • rehash function으로는 Linear Probing 사용

def get(self, key):
	startslot = self.hashfunction(key, len(self.slot))

	data = None
	stop = False
	found = False
	position = startslot

	while self.slots[position] != None and
					not found and not stop:
		# 값을 찾은 경우 while문 탈출
		if self.slots[position] == key:
			found = True
			data = self.data[position]
		else:
			position = self.rehash(position, len(self.slots))
			# 최초의 slot으로 돌아오지 않도록 제어
			if poisition == startslot:
				stop = True

	return data

def __getitem__(self, key):
	return self.get(key)

def __setitem__(self, key, data):
	self.put(key, data)
  • position의 rehash를 탈출하는 조건은 3가지로 분류
    1. rehash를 통해 얻은 position 값이 입력 값의 key 값과 일치하는 경우: 검색에 성공한 case
    2. rehash를 통해 얻는 position 값이 hashfucntion으로 얻은 값과 일치하는 경우: 원값으로 돌아올 때 까지 탐색에 실패한 case
    3. rehash를 통해 얻은 position 값의 slot이 비어있는 경우: Linear Search를 하다가 값이 존재하지 않는 case
  • [] 연산의 사용을 위해 __getitem__과 __setitem__ method를 overloading

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class HashTable:
    def __init__(self):
        self.size = 11
        self.slots = [None] * self.size
        self.data = [None] * self.size
 
 
    def put(self, key, data):
        hashvalue = self.hashfunc(key, len(self.slots))
 
        if self.slots[hashvalue] == None:
            self.slots[hashvalue] = key
            self.data[hashvalue] = data
        else:
            if self.slots[hashvalue] == key:
                self.data[hashvalue] = data
            else:
                nextslot = self.rehash(hashvalue, len(self.slots))
                while self.slots[nextslot] != None and \
                                self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot, len(self.slots))
 
                if self.slots[nextslot] == None:
                    self.slots[nextslot] = key
                    self.data[nextslot] = data
                else:
                    self.data[nextslot] = data
 
 
    def hashfunc(self, key, size):
        return key % size
 
 
    def rehash(self, oldhash, size):
        return (oldhash+1) % size    
 
 
    def get(self, key):
        startslot = self.hashfunc(key, len(self.slots))
 
        data = None
        stop = False
        found = False
        position = startslot
        while self.slots[position] != None and \
                            not found and not stop:
            if self.slots[position] == key:
                found = True
                data = self.data[position]
            else:
                position = self.rehash(position, len(self.slots))
                if position == startslot:
                    stop = True
        return data
 
 
    def __getitem__(self, key):
        return self.get(key)
 
 
    def __setitem__(self, key, data):
        self.put(key, data)
cs

[Reference]
Problem Solving with Algorithms and Data Structure
윤성우의 열혈 자료구조


Priority Queue(우선순위 큐)

Introduction

  • 우선순위 큐는 들어간 순서에 상관 없이 우선순위가 높은 데이터가 먼저 나오는 자료구조
  • 우선순위 큐를 구현하는 방법
    1. 배열을 기반으로 구현: 데이터를 삽입 및 삭제하는 과정에서 데이터를 한 칸씩 뒤로 밀거나 한 칸씩 당기는 연산을 수반해야 함 + 원소의 삽입 위치를 찾기 위해 배열의 저장된 모든 데이터와 우선순위의 비교를 진행해야 할 수도 있음
    2. 연결 리스트를 기반으로 구현: 원소의 삽입 위치를 찾기 위해 첫 번째 노드에서부터 마지막 노드까지에 저장된 데이터들과 우선순위의 비교를 진행해야 할 수도 있음
    3. 힙을 이용하여 구현
  • 우선순위 큐의 logarithmic한 성능을 보장하기 위해 항상 이진 트리의 균형을 맞추어야 함
  • 완전 이진 트리(Complete Binary Tree)의 구조를 이용하여 트리의 균형을 맞춤
  • 힙을 기반으로 한 우선순위 큐는 트리의 높이에 해당하는 수만큼 비교연산(삽입 및 삭제를 위한)을 진행하게 됨
    • 힙 기반 데이터 저장의 시간 복잡도: O(logN)
    • 힙 기반 데이터 삭제의 시간 복잡도: O(logN)

우선순위 큐 구현에 필요한 기본 연산

  • BinaryHeap(): 새로운 Binary Heap을 생성
  • insert(item): heap에 새로운 원소 추가
  • findMin(): heap에서 가장 작은 값을 가진 원소의 값 반환
  • delMin(): heap에서 가장 작은 값을 가진 원소의 값 반환 후 삭제
  • isEmpty(): heap의 empty 여부를 boolean 값으로 반환
  • size(): heap의 원소 개수 반환
  • buildHeap(list): 원소들을 지닌 list로 heap을 새로이 생성

우선순위 큐의 로직

# 클래스와 생성자
class BinaryHeap:
	def __init__(self):
			self.heapList = [0]
			self.currentSize = 0
  • 배열의 0번 index는 사용하지 않음
  • 따라서 힙에 저장된 노드의 전체 개수와 마지막 노드의 index가 일치

def percUp(self, i):
	# parent 원소가 0보다 큰 index에 위치해 있을 때 까지 반복
	while i // 2 > 0:
		# 추가된 원소가 Parent 원소보다 우선순위가 높을 경우 두 값을 swap
		if self.heapList[i] < self.heapList[i // 2]:
			tmp = self.heapList[i // 2]
			self.heapList[i // 2] = self.heapList[i]
			self.heapList[i] = tmp
		i = i // 2

def insert(self, item):
	self.heapList.append(k)
	self.currentSize = self.currentSize + 1
	self.percUp(self.currentSize)
  • 가장 쉽고 효율적인 원소 추가 방법은 list의 가장 끝에 원소를 새로이 추가해주는 것
  • 새로이 추가된 원소와 그 Parent의 값을 비교하며, 추가된 원소의 우선순위가 더 높을 경우 두 값을 swap하는 방식을 반복해서 수행
  • 우선순위가 높은 신규 원소의 값을 위의 level로 올리는 과정에서 heap의 구조적 특징을 다시 회복

def percDown(self, i):
	# Child 원소가 Heap의 사이즈보다 작거나 같을 때 까지 반복
	while (i * 2) <= self.currentSize:
		# 우선순위가 더 작은 child 노드의 값 선정
		mc = self.minChild(i)
		# child 노드보다 우선순위가 낮을 경우, 두 값을 swap
		if self.heapList[i] > self.heapList[mc]:
			tmp = self.heapList[i]
			self.heapList[i] = self.heapList[mc]
			self.heapList[mc] = tmp
		# 다음 비교를 위해 자식 노드의 index로 변경
		i = mc

def minChild(self, i):
	# Left child node만 있는 경우(완전 이진 트리이므로)
	if i * 2 + 1 > self.currentSize:
		return i * 2

	# 두 개의 child node가 모두 있는 경우
	else:
		if self.heapList[i*2] < self.heapList[i*2+1]:
			return i * 2
		else:
			return i * 2 + 1

def delMin(self):
	retval = self.heapList[1]
	self.heapList[1] = self.heapList[self.currentSize]
	self.currentSize = self.currentSize - 1
	self.heapList.pop() # 이미 루트 자리로 이동하였으므로, 제거
	self.percDown(1)
	return retval
  • 루트 노드의 원소가 가장 우선순위가 높은 원소이기 때문에 우선순위가 높은 원소를 찾는 것은 매우 쉬움
  • 어려운 부분은 루트 노드를 삭제한 후 다시금 heap의 구조를 정상적으로 회복시키는 것
  • 가장 끝에 있는 원소를 루트 노드의 자리로 이동
    • 루트 노드로 이동한 원소와 해당 노드의 자식 노드들의 우선순위를 비교해가며 swap 해가는 방식을 통해 다시 heap의 특성을 갖출 수 있음

def buildHeap(self, alist):
	i = len(alist) // 2
	self.currentSize = len(alist)
	self.heapList = [0] + alist[:]
	while(i > 0):
		self.percDown(i)
		i = i - 1
  • heap이 완전 이진 트리로 구성되어 있기 때문에 i는 항상 최하단의 바로 윗 레벨에서 시작하게 됨
  • i를 줄여나가며 percDown 연산을 수행하기 때문에 결국 모든 원소의 값이 자신의 우선순위에 맞는 자리를 찾아가게 됨

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class BinaryHeap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0
 
    def percUp(self, i):
        while i // 2 > 0:
            if self.heapList[i] < self.heapList[i // 2]:
                tmp = self.heapList[i // 2]
                self.heapList[i // 2= self.heapList[i]
                self.heapList[i] = tmp
            i = i // 2
 
    def insert(self, k):
        self.heapList.append(k)
        self.currentSize = self.currentSize + 1
        self.percUp(self.currentSize)
 
    def percDown(self, i):
        while i * 2 <= self.currentSize:
            mc = self.minChild(i)
            if self.heapList[i] > self.heapList[mc]:
                tmp = self.heapList[i]
                self.heapList[i] = self.heapList[mc]
                self.heapList[mc] = tmp
            i = mc
 
    def minChild(self, i):
        if i * 2 + 1 > self.currentSize:
            return i * 2
        else:
            if self.heapList[i*2< self.heapList[i*2+1]:
                return i * 2
            else:
                return i * 2 + 1
 
    def delMin(self):
        retval = self.heapList[1]
        self.heapList[1= self.heapList[self.currentSize]
        self.currentSize = self.currentSize - 1
        self.heapList.pop()
        self.percDown(1)
        return retval
 
    def findMin(self):
        retval = self.heapList[1]
        return retval
 
    def isEmpty(self):
        if self.currentSize == 0:
            return True
        else:
            return False
 
    def size(self):
        retval = self.currentSize
        return retval
 
    def buildHeap(self, alist):
        i = len(alist) // 2
        self.currentSize = len(alist)
        self.heapList = [0+ alist[:]
        while(i > 0):
            self.percDown(i)
            i = i - 1
cs

[Reference]
Problem Solving with Algorithms and Data Structure
윤성우의 열혈 자료구조


지옥에서 온 Git (2)

git add의 원리

  • 모든 file의 이름은 index에 담겨 있음

  • 각각의 file 내용은 object에 담겨 있음
  • Git에서는 file의 이름이 달라도 같은 내용을 담고 있는 경우, 같은 object를 참조(하단 설명 참고)

objects 파일명의 원리

  • Git은 파일의 내용을 기반으로 object 파일의 이름을 생성
  • 이는 중복 데이터를 저장하는데 매우 효율적으로 작동
    • 같은 내용을 가진 파일에 대한 object를 생성하지 않기 때문
  • 파일의 내용 + additional information으로 이루어진 값을 "sha1"에 통과시키기 때문에 같은 내용을 지닌 파일은 결국 같은 object명 사용하게 됨

commit의 원리

  • commit message 또한 object화 되어 생성됨

  • commit message에는 현재 version이 참조하는 object들이 tree 형태로 담겨 있음(각각의 버전에 대한 Snap shot)

  • 최초 commit 이후에는 parent commit을 제공하며, 이는 이전 version의 commit message를 참조


  • local에서의 폴더 정보 역시 'tree' 자료형으로 저장됨
  • 즉, object는 tree(directory), blob(file), commit(commit message) 중에 하나의 자료형을 가짐

status의 원리

  • index 내 파일들과 local 파일들을 비교하여 add 필요 여부 판단
  • index 내 파일들과 최신 commit message의 tree 내 파일들의 차이를 비교하여 commit 대기 상태 여부 판단


'Software Convergence > Git' 카테고리의 다른 글

지옥에서 온 Git (3)  (0) 2018.08.27
지옥에서 온 Git (1)  (1) 2018.08.22

지옥에서 온 Git (1)

수업 소개

  • git은 버전 관리를 위한 시스템이자 프로그램
  • 우리는 report.doc, report_final.doc, report_final_real_final.doc과 같이 여러 개의 버전(네이밍)으로 이미 버전 관리를 경험한 적이 있음
  • What is the Version Control System?
    • 여러 가지 역할과 의미를 가지고 있기 때문에 한 번에 이해하기는 힘듦
    • 가장 핵심은 고정된 파일의 이름으로 버전 관리가 가능하다는 것
    • 그 외에도 Backup, Recovery, Collaboration의 기능을 사용할 수 있도록 도와줌
  • 여러 가지 버전 관리 시스템들이 존재
    • CVS - SVN - GIT 의 순서로 발전한 버전 관리 시스템
    • Dropbox, Google Drive 등도 버전 관리 시스템의 일종
  • git은 많은 기능 때문에 복잡할 수 있음
    • 그러나 복잡한 프로젝트일수록 git의 도움으로 Complexity를 줄일 수 있음
    • 따라서 git을 아는 것은 실무 프로젝트의 복잡성을 줄여주는데 큰 도움이 됨

저장소 만들기

# 프로젝트 디렉토리 생성
mkdir git_devfon

# 프로젝트 디렉토리로 이동
cd git_devfon

# 현재 디렉토리를 git의 버전 저장소로 초기화
git init 


  • .git 디렉토리에 버전 관리 정보가 저장되기 때문에 중요한 디렉토리!

git이 관리할 대상으로 파일 등록

  • git은 기본적으로 새로운 파일을 관리하지 않음
  • 파일을 관리하기 위해서는 관리 대상으로 따로 등록을 해주어야 함
  • 파일을 이렇게 선택적으로 관리하는 이유는 프로젝트를 진행함에 있어 주가 되는 파일과 임시 파일 등이 있기 때문
    • 임시 파일의 경우 굳이 버전 관리 해줄 필요가 없음
# 새로운 파일을 생성
vim test.txt

# git이 파일을 추적하도록 명령
git add test.txt

# 프로젝트 디렉토리의 상태를 확인
git status


버전 만들기 (commit)

# git 닉네임 설정
git config --global user.name "자신의 닉네임"

# git 이메일 설정
git config --global user.email "자신의 이메일"

# commit message 작성 in Vim
git commit

# log 확인으로 committer와 committer의 이메일, commit 날짜 확인 가능 
git log



  • 닉네임과 이메일 설정은 최초 1번만 해주면 됨
  • 파일 생성 및 수정 후, 버전 만들기 전 add를 재설정해주어야 함
    • add만 사용하면 선택적으로 파일에 대한 commit 수행 가능
    • add로 디렉토리를 추적시키면, 해당 디렉토리는 commit 대기 상태(stage area)가 됨
    • commit이 된 결과가 저장되는 곳이 repository
    • git commit -a 로 수정된 모든 파일에 대한 commit 수행할 수도 있음(add된 파일에 한해서만)
    • git commit -m 'message' 통해 vim editor 들어가지 않고 commit message 바로 전달 가능

변경사항 확인하기

  • 버전 간 소스 코드의 차이점과 과거 내용을 알 수 있음
  • 과거 버전으로 돌아갈 수 있음

# 버전 간의 코드 차이를 출력하고 싶을 때
git log -p



  • 빨간색 네모는 version 4에서의 test3.txt
  • 파란색 네모는 version 5에서의 test3.txt

# 버전 간 차이점을 비교할 때
git diff 'version id'..'version id2'
  • 각각의 commit id는 중복되지 않는 고유한 것
  • 버전 간 비교를 할 때 이전 버전에서 없었던 파일은 /dev/null 과 같이 표기됨

# git add 하기 전 자신의 수정 내용을 이전 버전의 것과 비교할 때
git diff 



  • 모든 수정 파일들의 add 후에는 아무 것도 출력하지 않음
    • 새로운 파일의 생성 이후에는 비교값이 없으므로 작동 X

과거 버전으로 돌아가기

# 해당 버전 id로 돌아가는 명령어
git reset --hard "commit id"



  • second commit 이후 정보들이 log에 나타나지 않는 것을 볼 수 있음
  • --hard 옵션 외에도 --soft, --mixed 옵션 등이 있음
  • git은 어떠한 정보도 잘 삭제하지 않음. 따라서 눈에는 해당 버전 id 이후의 log가 보이지 않지만, commit 내용은 사실 남아있음(차후 다시 공부)
  • repository를 공유한 이후부터는 절대 reset 명령어를 사용하면 안 됨. 자신의 local에서 작성할 때 까지만 reset 명령어의 사용 가능

# 해당 버전 id의 commit을 취소한 내용으로 새로운 버전을 만드는 명령어
git revert "commit id"



  • second commit에서 'test for github'를 'test for github2'으로 수정
  • second commit을 revert한 결과 'test for github2'로 수정한 commit이 취소되고 다시 'test for github'으로 변경되어 저장되는 것을 확인 가능


'Software Convergence > Git' 카테고리의 다른 글

지옥에서 온 Git (3)  (0) 2018.08.27
지옥에서 온 Git (2)  (0) 2018.08.24

BeautifulSoup을 이용한 파이썬 웹 크롤러 만들기


1) 아나콘다 다운로드

주피터 노트북, BeautifulSoup 등을 비롯한 다양한 파이썬 패키지를 미리 포함하고 있는 아나콘다는 실습 뿐 아니라 실 개발 환경으로도 사용될 수 있는 편리한 파이썬 배포판이다. 특히, 주피터 노트북은 프로그램을 통해 분석한 결과를 가시적으로 모니터링할 수 있다는 큰 장점으로 인해 데이터 사이언스 진영에서 Apache Zeppelin과 함께 큰 인기를 끌고 있다.



따라서 우리는 주피터 노트북을 사용하기 위해 먼저 아나콘다를 다운로드 하기로 한다. 먼저 '링크' 에 접속하여 본인의 운영체제에 맞는 설치 파일을 받아준다. 이후, 설치 과정에서는 특별한 설정을 하지 않고 default 값으로 설치를 완료해준다.




설치가 마무리되면, anaconda prompt를 실행시켜준다.

아래와 같이 명령어 프롬프트가 나타나면 아래의 명령어를 실행시킨다.



jupyter notebook 

잠깐의 로딩 과정을 거친 후, 다음과 같은 화면을 지닌 브라우져가 실행될 것이다. 




해당 화면에서 다음과 같이 'Python3'의 새 프로젝트를 생성해준다.




2) 크롤러 작성

새로운 프로젝트가 생성되면 가장 위에는 아래와 같은 코드를 작성해준다. BeautifulSoup는 웹 크롤링을 위한 파이썬의 오픈 소스 라이브러리이며, urlopen은 문자 그대로 우리가 지정한 url을 파이썬을 통해 열기 위해 import 해야 하는 모듈이다.

from bs4 import BeautifulSoup
from urllib.request import urlopen

cf. Jupyter Notebook에서는 ctrl + enter 로 해당 라인을 실행시킬 수 있으며, 편집 모드와 명령 모드는 esc키를 통해 전환이 가능하다. 또한 명령 모드에서 b 키를 통해 새로운 라인을 이전 라인의 below에 생성할 수 있다. 따라서 a 키를 통해서는 이전 라인의 above에 새로운 라인을 생성할 수 있다.


다음으로, 크롤링을 하고자 하는 사이트의 url을 지정해준다. 우리는 네이버 영화의 평점을 읽어올 것이기 때문에 네이버 무비의 url을 사용한다.

url = "http://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=cur&date=20180802" 

이제 해당 url을 읽어와 그 결과를 page에 담고, BeautifulSoup을 통해 page를 html tag로 parsing하여 담는다.

page = urlopen(url)
soup = BeautifulSoup(page, "html.parser")
soup



이후, html을 parsing한 soup의 html 코드를 읽어보면 우리가 찾고자 하는 영화의 제목은 div tag 중 tit5라는 class에 속한 값임을 알 수 있다. 따라서 다음과 같은 코드를 통해 모든 영화의 목록을 읽어온다.

soup.find_all('div','tit5')




이어서 평점의 경우, td tag의 point라는 class에 속함을 알 수 있다. 


따라서 다음 코드로 해당 날짜에 존재하는 모든 영화의 제목과 평점을 읽어온다.
 
title_n = soup.find_all('div', 'tit5')

# 전체 영화의 제목 뽑아옴
movie_name = [soup.find_all('div', 'tit5')[n].a.string for n in range(0, len(title_n))]

# 전체 순위의 평점 뽑아옴
movie_point = [soup.find_all('td', 'point')[n].string for n in range(0, len(title_n))]

마지막으로, index를 활용하여 각 영화의 제목과 평점을 key와 value 값으로 보기 좋게 나열해볼 수 있다.
movie_dict = {}

for i in range(0, len(title_n)): 
    movie_dict[movie_name[i]] = movie_point[i]

movie_dict




실습한 바와 같이, 파이썬을 이용하여 웹 크롤러를 작성하는 것은 편리한 라이브러리 덕분에 기본적인 파이썬 문법과 html 및 css 태그에 대한 이해만 있다면 큰 어려움 없이 작성이 가능하다. 다음 포스트에서는 특정 기간 동안 영화의 평균 평점을 구하는 작업과 특정 영화가 해당 기간 동안 평점이 어떻게 변화하였는지 등의 코드도 작성해본다.


+ Recent posts