시스템 콜을 설명하시오.
경쟁 상태에 대해 설명하시오.

운영체제는 커널모드와 사용자 모드로 나뉘어 구동됨. 운영체제에서 프로그램이 구동되는데 있어 파일을 읽어 오거나, 파일을 쓰거나, 혹은 화면에 메시지를 출력하는 등 많으 부분이 커널 모드를 사용한다.

시스템 콜은 이러한 널 영역의 기능을 사용자 모드가 사용 가능하게, 즉 프로세스가 하드웨어에 직접 접근해서 필요한 기능을 사용할 수 있게 해준다. 

 

시스템 콜의 유형

2.4.1 프로세스 제어(Process Control)

  • 끝내기(end), 중지(abort)
  • 적재(load), 실행(execute)
  • 프로세스 생성(create process)
  • 프로세스 속성 획득과 설정(get process attribute and set process attribute)
  • 시간 대기(wait time)
  • 사건 대기(wait event)
  • 사건을 알림(signal event)
  • 메모리 할당 및 해제 : malloc, free

 

2.4.2 파일 조작(File Manipulation)

  • 파일 생성(create file), 파일 삭제(delete file)
  • 열기(open), 닫기(close)
  • 읽기(read), 쓰기(write), 위치 변경(reposition)
  • 파일 속성 획득 및 설정(get file attribute and set file attribute)

 

2.4.3 장치 관리(Devide Management)

  • 장치를 요구(request devices), 장치를 방출release device)
  • 읽기, 쓰기, 위치 변경
  • 장치 속성 획득, 장치 속성 설정
  • 장치의 논리적 부착(attach) 또는 분리(detach)

 

2.4.4 정보 유지(Information Maintenance)

  • 시간과 날짜의 설정과 획득(time)
  • 시스템 데이터의 설정과 획득(date)
  • 프로세스 파일, 장치 속성의 획득 및 설정

 

2.4.5 통신(Communication)

  • 통신 연결의 생성, 제거
  • 메시지의 송신, 수신
  • 상태 정보 전달
  • 원격 장치의 부착 및 분리

 

 

경쟁상태란 공유 자원에 대해 여러 프로세스가 동시에 접근을 시도할 때, 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태이다.

커널 코드 실행 중에 인터럽트가 발생할 경우

프로세스가 시스템 콜을 하여 커널모드로 진입해서 작업을 수행하는 도중에 문맥 교환이 발생할 경우

멀티 프로세서에서 공유 메모리 내의 커널 데이터에 접근할 경우

이밖에도 멀티스레드 환경에서 두 개 이상의 스레드가 공통의 전역변수에 접근할 경우 등에서 race condition이 발생할 수 있다

 

경쟁 상태의 예

 

'스터디 관련' 카테고리의 다른 글

CS 스터디 03회차, ~ 6월 30일(목)  (0) 2022.06.30
CS스터디 02회차, ~06월 09일(목)  (0) 2022.06.09
CS스터디 01회차, ~06월 02일(목)  (0) 2022.06.02
2/3(수) 과제  (0) 2021.02.03
1/6일 스터디 과제  (0) 2021.01.06
쓰레드와 프로세스의 차이는 무엇인가?
인터럽트(interrupt)에 대해 설명하시오.

프로세스는 운영체제로부터 자원을 할당받는 작업의 단위이다.

쓰레드는 할당받은 자원을 이용하는 실행의 단위이고 프로세스 내에 여러개가 생길 수 있다.

- 프로그램을 실행하는 순간 파일은 컴퓨터 메모리에 올라가게 되고, 이 상태를 동적 상태라고 이야기 하며 이 상태의 프로그램을 프로세스라고 한다. 따라서 실행중인 프로그램을 프로세스라고 생각하면 된다.

그런데, 프로그램들이 점차 복잡해지기 시작하면서 하나의 프로세스만으로는 벅차게 되었다. 이때 가장 먼저 생각나는 해결책은 프로세스를 여러개 만드는 것이겠지만, 운영체제는 안정성을 위하여 자신에게 할당된 메모리 내역 정보에만 접근 할 수 있도록 제약을 두고 있고 이를 벗어나는 정보에 접근하면 오류가 나기에 불가능했다. 

이러한 문제를 해결하기 위해서 쓰레드가 나왔다. 쓰레드는 프로세스와 다르게 쓰레드 간 메모리를 공유하며 작동한다. 쓰레드는 프로세스의 코드에 정의된 절차에 따라 실행되는 특정한 수행 경로다.

프로세스와 쓰레드의 작동 방식 차이

프로세스들이 운영체제로부터 별도의 메모리 영역을 할당받은 모습 (이미지 출처:  Heee's Development Blog )

 

스레드들이 프로세스의 Code/Data/Heap 메모리 영역을 공유하는 모습 (이미지 출처:  Heee's Development Blog )

 

여기서도 차이가 나는데, 프로세스를 실행중에 크래쉬가 나면, 다른 프로세스에 영향을 주지 않는다.

하지만, 한 쓰레드에 오류가 난다면, 같은 프로세스 내의 모든 쓰레드가 강제로 종료된다.

CPU가 작업을 할때는 쓰레드를 최소 단위로, 운영체제가 작업을 할때는 프로세스가 최소 단위로 삼아져서 작업이 된다.

하나의 프로세스는 하나 이상의 쓰레드를 갖게된다.

 

 

 

 

주변 장치와 입출력 장치는 CPU나 메모리와 달리 interrup라는 메커니즘을 통해 관리된다. 입출력 연산이 CPU 명령 수행속도보다 현저히 느리기 때문이다.

인터럽트의 정의는 CPU가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 예외상황이 발생하여 처리가 필요할 경우에 마이크로프로세서에게 알려 처리할 수 있도록 하는 것이다.

즉, CPU의 처리 속도가 입출력장치속도보다 빨라서 다른 업무를 먼저 수행하고 있다가, 입출력장치가 일을 끝내고 CPU에게 이를 알리면 CPU는 다시 관련된 업무를 진행하는 과정으로 진행이 된다. 이 과정에서 CPU에게 알리는 과정이 인터럽트이다.

 

참고 블로그: https://velog.io/@raejoonee/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EC%B0%A8%EC%9D%B4

 

프로세스와 스레드의 차이

프로세스와 스레드의 차이는 프로세스와 스레드, 그리고 프로그램이 작동하는 방식에 대해서 잘 이해하고 있는지 확인하기 위해 기술면접에서 단골 질문 사항으로 나온다.

velog.io

https://velog.io/@adam2/%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8

 

[OS기초] 인터럽트 제대로 이해하기

주변장치와 입출력 장치는 CPU나 메모리와 달리 인터럽트라는 메커니즘을 통해 관리된다. 그래서 인터럽트, 왜 하는거요? 그 이유는 입출력 연산이 CPU 명령 수행속도보다 현저히 느리기 때문이

velog.io

 

'스터디 관련' 카테고리의 다른 글

CS 스터디 04회차, ~ 7월 7일(목)  (0) 2022.07.07
CS스터디 02회차, ~06월 09일(목)  (0) 2022.06.09
CS스터디 01회차, ~06월 02일(목)  (0) 2022.06.02
2/3(수) 과제  (0) 2021.02.03
1/6일 스터디 과제  (0) 2021.01.06
파이프라인이란 무엇인가?
중앙처리장치(CPU)의 작동원리

컴퓨터 구조 분야에는 일곱가지의 위대한 아이디어가 있다. (Computer Organization and Design Mips Edition, David A. Patterson) 

설계를 단순화하는 추상화(abstraction)
자주 생기는 일을 빠르게(common case fast)
병렬성을 통한 성능 개선(parallelism)
파이프라이닝을 통한 성능 개선(pipelining)
예측을 통한 성능 개선(prediction)
메모리 계층구조(memory hierarchy)
여유분을 이용한 신용도 개선(dependability)

이 중에서 파이프라이닝은 컴퓨터 구조에서 많이 볼 수 있는 병렬성의 특별한 형태이다. 연탄나르기를 할때 모두가 서있고 연탄을 바로 옆사람에게 넘기는 구조라고 생각하면 된다. 

더 자세히 이야기하자면, 여러 명령어가 중첩되어 실행되는 구현 기술이다. 구체적인 예시로 세탁을 할 때를 들어보겠다. 빨래를 많이 안해본 사람들은 다음과 같이 파이프라이닝되지 않은(nonpipelined) 세탁 방법을 사용한다.

1. 세탁기에 한 뭉치의 더러운 옷을 넣는다.
2. 세탁기 작동이 끝나면 젖은 옷을 건조기에 넣는다.
3. 건조기 작동이 끝나면 건조된 옷을 탁자 위에 놓고 접는다.
4. 접는 일이 끝나면 같은 방 친구에게 옷을 옷장에 넣어달라고 부탁한다.
5. 이 사이클을 반복한다.

그렇다면 파이프라이닝된 세탁 방법은 무엇일까?

 

세탁기 작동이 끝나고 젖은 옷을 건조기에 넣을때, 다른 옷감을 세탁기에 넣어 세탁하는 것이다. 이런 식으로 병렬되게 세탁을 하면 전체 태스크의 시간이 줄어든다.

파이프라이닝되지 않은 세탁(위)과 파이프라이닝된 세탁(아래)

중요한 점은 파이프라이닝을 한다고 해서 각 단계에서 소요되는 시간이 줄어드는 것은 아니다. 파이프라이닝되기 전의 세탁기 작동시간이 1시간이었다면, 파이프라이닝되었을 때에도 1시간의 작동시간을 갖는다.

할 일이 충분히 많고, 위의 그림처럼 모든 단계가 거의 같은 시간이 걸린다면, 파이프라이닝에 의한 속도 향상은 파이프라이닝의 단계 수와 같다. 위의 경우에는 세탁, 건조, 개기, 넣기의 네 단계가 있으므로 단계 수는 4이다. 

프로세서에서는 단계가 다음처럼 나뉘어진다.

1. 메모리에서 명령어를 가져온다
2. 명령어를 해독하는 동시에 레지스터를 읽는다. MIPS 명령어는 형식이 규칙적이므로 읽기와 해독이 동시에 일어날 수 있다.
3. 연산을 수행하거나 주소를 계산한다.
4. 데이터 메모리에 있는 피연산자에 접근한다.
5. 결과값을 레지스터에 쓴다.

프로세서는 논리적으로 데이터패스와 제어 유닛의 두 부분으로 구성된다. 각각은 프로세서의 근육과 두뇌에 해당한다. 데이터패스는 연산을 수행하고, 제어 유닛은 명령어가 뜻하는 바에 따라 데이터패스, 메모리, 입출력장치가 할 일을 지시한다. 프로세서 내부에는 또 다른 종류의 메모리가 있는데 이것을 캐시메모리라고 한다. 캐시 메모리는 DRAM의 버퍼 역할을 하는 작고 빠른 메모리이다.

CPU 내부 구조

 

'스터디 관련' 카테고리의 다른 글

CS 스터디 04회차, ~ 7월 7일(목)  (0) 2022.07.07
CS 스터디 03회차, ~ 6월 30일(목)  (0) 2022.06.30
CS스터디 01회차, ~06월 02일(목)  (0) 2022.06.02
2/3(수) 과제  (0) 2021.02.03
1/6일 스터디 과제  (0) 2021.01.06
캐시(cache)란 무엇인가?
메모리의 구조는 어떻게 구성되어 있는가? (데이터, 스택 등의 단어를 사용하시오)

캐시는 프로세서가 단기간에 필요로 할 가능성이 높은 정보를 임시로 저장할 목적으로 사용한다. 시스템이 정보를 한 곳에서 다른 곳으로 이동시키는 일에 많은 시간을 보내기 때문에 여러개의 복사과정들을 가능한 한 빠르게 동작하도록 하기 위해서 만들어졌다.

일반적으로 캐시는 보다 크고 느린 디바이스에 저장된 데이터 객체를 위한 준비영역으로 사용하는 작고 빠른 저장장치다. 메모리 계층구조?

일반적으로 계층구조에서 낮은 곳에 위치한 디바이스들은 더 긴 접근시간을 가지며, 따라서 이러한 긴 접근시간을 줄이기 위해서 더 큰 블록을 사용하는 추세다. 

캐시의 장점은 다음 두가지이다.

시간지역성 활용하기. 동일한 데이터 객체는 시간 지역성 때문에 여러 번 재사용될 가능성이 있다. 일단 어떤 데이터 객체에 첫 미스가 발생했을 때 캐시로 복사되고 난 후에는 이 객체로 연속적인 다수의 적중을 기대할 수 있다. 캐시가 다음 아래단계에 있는 저장장치보다 더 빠르기 때문에 이와같은 차후의 적중이 처음의 미스보다 더 빨리 서비스될 수 있다.

공간 지역성 활용하기 블록들은 대개 여러개의 데이터 객체들을 포함한다. 공간지역성으로 인해 미스가 발생한 후에 하나의 블록을 복사하는 비용은 차후에 이 블록 내의 다른 객체들을 참조하기 때문에 줄어들게 된다.

메모리의 구조는 다음과 같다.

프로그램 코드와 데이터. 코드는 모든 프로세스들이 같은 고정 주소에서 시작하며, 다음에 C 전역변수에 대응되는 데이터 위치들이 따라온다. 코드와 데이터 영역은 실행가능 목적파일로부터 직접 초기화된다.

힙. 코드와 데이터 영역 다음으로는 런타임 힙이 따라온다. 크기가 고정되어있는 코드, 데이터 영역과 달리 힙은 프로세스가 실행되면서 C 표준함수인 malloc이나 free를 호출하면서 런타임에 동적으로 그 크기가 늘었다 줄었다 한다. 

공유 라이브러리. 주소공간의 중간 부근에 C 표준 라이브러리나 수학 라이브러리와 같은 공유 라이브러리의 코드와 데이터를 저장하는 영역이 있다. 공유 라이브러리 개념은 강력하지만 다소 어려운 개념이다. 

스택. 사용자 가상메모리 공간의 맨 위에 컴파일러가 함수 호출을 구현하기 위해 사용하는 사용자 스택이 위치한다. 힙과 마찬가지로 사용자 스택은 프로그램이 실행되는 동안에 동적으로 늘어났다 줄어들었다 한다. 특히 함수를 호출할 때마다 스택이 커지며, 함수에서 리턴될 때는 줄어든다.

커널 가상메모리. 주소공간의 맨 윗부분은 커널을 위해 예약되어 있다. 응용프로그램들은 이 영역의 내용을 읽거나 쓰는 것이 금지되어 있으며, 마찬가지로 커널 코드 내에 정의된 함수를 직접 호출하는 것도 금지되어 있다. 대신, 이런 작업을 수행하기 위해서는 커널을 호출해야한다.

 

CS:APP 3rd Edition, Randal E. Bryant, Pearson

'스터디 관련' 카테고리의 다른 글

CS 스터디 03회차, ~ 6월 30일(목)  (0) 2022.06.30
CS스터디 02회차, ~06월 09일(목)  (0) 2022.06.09
2/3(수) 과제  (0) 2021.02.03
1/6일 스터디 과제  (0) 2021.01.06
이번 스터디 과제,,  (0) 2020.12.31

외부스택vs내부스택. 메모리계층 구조. 스택포인터&프레임포인터에 대해 조사하고, 복사생성자와 대입연산자의 차이점 구분, 연산자 오버로딩 복습(오버로딩과 오버라이딩 설명&차이점)

 

메모리의 구조

 

  프로그램을 실행하기 위해 컴퓨터가 가장 먼저 하는 일은 프로그램 로드(load)이다. 흔히 로딩(loading)이라고 부르는 과정을 의미하며, 메모리에 프로그램을 불러오는 것을 의미한다. 이때, 프로그램에서 사용되는 변수나, 프로그램의 코드에 대한 메모리 공간은 각각 다르다. 

 프로그램은 크게 4가지 공간을 메모리에 할당받는다. 스택영역, 힙영역, 데이터영역, 코드영역으로 나뉜다.

스택영역

스택(stack)영역은 함수의 호출에서 얻게되는 지역변수나 매개변수가 저장되는 영역이다. 함수가 호출되면서 스택영역을 할당받으며, 함수와 함께 소멸한다. 이때, 스택영역에 저장되는 함수의 호출정보를 스택프레임(stack frame)이라고 말한다.

스택의 ADT를 살펴보면 push를 통해 데이터를 저장하고, pop으로 데이터를 인출하는 후입선출의 구조이다. 쉽게 생각하면 탄창과 같은 구조이기에 나중에 넣은 총알을 가장 먼저 발사하게 되는 구조이다.

힙영역

힙(heap)영역은 사용자가 직접 관리할 수 있고, "해야 하는"(must to) 메모리 영역이다. 사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다.

데이터영역

데이터(data)영역은 프로그램의 전역변수와 정적(static)변수가 저장되는 영역이다. 프로그램의 시작에 할당이 되며 프로그램 종료시 소멸된다.

코드영역

코드(code)영역은 실행할 프로그램의 코드가 저장되는 영역으로 text영역이라고도 한다.

스택 포인터(stack pointer, sp)

 

CPU 안에는 스택에 데이터가 채워진 위치를 가리키는 레지스터인 스택 포인터(SP)를 갖고 있다. 즉, 스택 포인터란 레지스터중 하나를 의미한다. 스택포인터가 가리키는 곳까지가 데이터가 채워진 영역이고, 그 이후부터 스택 끝까지는 비어있는 영역이다.

스택에 새로운 항목이 추가되거나 스택에서 데이터가 제거되면, 스택 포인터의 값이 증가하거나 감소하는 식의 역할이다.

프레임 포인터(frame pointer, fp)

 

프레임 포인터 또한 레지스터이다. 함수 호출 이전의 스택포인터 위치를 기억하는 레지스터이다. 함수의 중첩시, 프레임 포인터의 값을 덮어쓸수 있기에 유의해야한다.

복사생성자 vs. 대입연산자

복사생성자는 객체의 복사가 이루어질때 생성되는 생성자이다.

대입연산자는 = 기호를 의미한다.

복사생성자와 대입연산자는 유사한 점이 많다. 복사 생성자의 대표적인 특성 세가지는 

  1. 정의하지 않으면 디폴트 복사생성자가 삽입된다.
  2. 디폴트 복사생성자는 얕은 복사를 진행한다. 
  3. 생성자 내에서 동적할당을 한다면, 깊은 복사가 필요하다면 직접 정의해야한다.

대입 연산자의 특징 세가지는 다음과 같다.

  1. 정의하지 않으면 디폴트 복사생성자가 삽입된다.
  2. 디폴트 복사생성자는 얕은 복사를 진행한다. 
  3. 생성자 내에서 동적할당을 한다면, 깊은 복사가 필요하다면 직접 정의해야한다.

차이점이 있다면, 호출되는 시점이다. 

복사생성자는 객체가 생성될때 호출되고, 대입연산자는 객체가 이미 생성된 후 호출된다.

 

 

오버로딩 vs. 오버라이딩

오버로딩과 오버라이딩은 이름뿐만 아니라 비슷한점이 많다. 정의부터 차근차근 살펴보면,

오버로딩은 함수를 여러개 정의하고, 매개변수의 유형이나 개수를 다르게하여 다양한 호출을 구분할수 있게 하는 것.

오버라이딩은 상속시, 상위 클래스가 갖고있는 함수를 하위클래스에서도 사용할 수 있도록 덮어쓰는 것 이다.

오버로딩의 경우, 함수 이름은 동일하고, 매개변수&타입은 다르며, 반환형은 상관없다.

오버라이딩의 경우, 함수 이름, 매개변수&타입 동일하며, 반환형 또한 동일해야한다.

 

 

 

 

'스터디 관련' 카테고리의 다른 글

CS스터디 02회차, ~06월 09일(목)  (0) 2022.06.09
CS스터디 01회차, ~06월 02일(목)  (0) 2022.06.02
1/6일 스터디 과제  (0) 2021.01.06
이번 스터디 과제,,  (0) 2020.12.31
소프트웨어 생명주기 6단계  (0) 2020.12.30

열혈 C++의 8장. 상속과 다형성을 복습하고 가상함수에 대해 익혀오는 과제가 있었다.

두개의 소스코드를 볼 수 있는데, FunctionVirtualOverride.cpp와 EmployeeManager4.cpp였다.

우선 가상함수에 대해서 간단히 짚고 넘어가보자.

가상함수란 virtual 키워드를 일반적인 함수 앞에 붙임으로 그 함수가 오버라이딩을 할 수 있게 해준다.

오버라이딩을 통해 한가지 기능을 다른 여러가지 기능으로 쓸수 있는 '다형성'을 만들어낼 수 있다.

또한, 가상함수가 선언되고 나면, 이 함수를 오버라이딩 하는 다른 함수는 virtual을 붙이지 않아도 가상함수가 된다.

/* FunctionVirtualOverride.cpp 
   열혈 C++ 347-348p           */
 
#include <iostream>
using namespace std;

class First //First 클래스 생성
{
public:
	virtual void MyFunc() { cout<<"FirstFunc"<<endl; } // FirstFunc을 출력하는 가상함수 MyFunc
};

class Second: public First //First를 상속받는 Second 클래스 생성
{
public:
	virtual void MyFunc() { cout<<"SecondFunc"<<endl; } //굳이 추가하지 않아도 가상함수가 되나 가독성을 위해 선언을 넣기도 한다.
};

class Third: public Second //Second를 상속받는 Third 클래스 생성
{
public:
	virtual void MyFunc() { cout<<"ThirdFunc"<<endl; }
};

int main(void)
{
	Third * tptr = new Third(); //Third 객체를 생성해서 Third형 포인터 변수로 참조
    Second * sptr = tptr; //Second형 포인터 변수로 참조
    First * fptr = sptr; //First형 포인터 변수로 참조
    
    fptr->MyFunc();  //포인터변수로 MyFunc 함수 호출
    sptr->MyFunc();
    tptr->MyFunc();
    delete tptr;
    return 0;
}

가상함수를 호출할때, 포인터의 자료형을 기반으로 호출대상이 결정되지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다.

/* EmployeeManager4.cpp
   열혈C++ 349-351p	 */

#include <iostream>
#include <cstring>
using namespace std;

class Employee
{
private:
	char name[100];
public:
	Employee(char * name)
    {
    	strcpy(this->name, name);
    }
	void ShowYourName() const
    {
    	cout<<"name: "<<name<<endl;
    }
    virtual int GetPay() const
    {
		return 0;
    }
    virtual void ShowSalaryInfo() const
    {}
};

class PermanentWorker : public Employee
{
private:
	int salary; //월 급여
public:
	PermanentWorker
};
class TemporaryWorker : public Employee
{

};
class SalesWorker : public PermanentWorker
{

};

class EmployeeHandler
{
private:
	Employee* emplist[50];
    {}
    void AddEmployee(Employee* emp)
    {
    	emplist[empNum++]=emp;
    }
    void ShowAllSalaryInfo() const
    {
    	for(int i=0; i<empNum; i++)
        	empList[i]->ShowSalaryInfo();
    }
    void ShowTotalSalary() const
    {
    	int sum=0;
        for(int i=0;i<empNum;i++)
        	sum+=empList[i]->GetPay();
        
        cout<<"salary sum: "<<sum<<endl;
    }
    ~EmployeeHandler()
    {
    	for(int i=0; i<empNum; i++)
        	delete empList[i];
    }
};

int main(void)
{
	//직원관리를 목적으로 설계된 컨트롤 클래스의 객체생성
    EmployeeHandler handler;
    
    //정규직 등록
    handler.AddEmployee(new PermanentWorker("KIM",1000));
	handler.AddEmployee(new PermanentWorker("LEE",1500));
    
    //임시직 등록
    TemporaryWorker* alba = new TemporaryWorker("Jung", 700);
    alba->AddWorkTime(5);
    handler.AddEmployee(alba);
    
    //영업직 등록
    SalesWorker* seller = new SalesWorker("Hong", 1000, 0.1);
    seller->AddSalesResult(7000);
	handler.AddEmployee(seller);
    
    //이번 달에 지불해야 할 급여의 정보
    handler.ShowAllSalaryInfo();
    
    //이번 달에 지불해야 할 급여의 총합
    handler.ShowTotalSalary();
    return 0;
]

'스터디 관련' 카테고리의 다른 글

CS스터디 02회차, ~06월 09일(목)  (0) 2022.06.09
CS스터디 01회차, ~06월 02일(목)  (0) 2022.06.02
2/3(수) 과제  (0) 2021.02.03
이번 스터디 과제,,  (0) 2020.12.31
소프트웨어 생명주기 6단계  (0) 2020.12.30

+ Recent posts