[블로그 프로젝트] Spring Boot Validation
Spring Boot 에서는 Validation을 통해 데이터의 유효성을 검증할 수 있다. public class SignUpRequestDto { @NotBlank @Email private String email; @NotBlank @Size(min = 8, max = 20) private String password; @NotBlank private String nickname; @NotBlank @Pattern(regexp = "^[0-9]{11,13}$") private String telNumber; @NotBlank private String address; private String addressDetail; @NotNull @AssertTrue private Boolean agreedPe..
2024.03.06
no image
state, useState
리액트의 state는 일반 자바스크립트 객체이고 랜더링을 유발하는 변수라고 볼 수 있다. state 값이 변경되면 리액트는 해당 컴포넌트를 다시 랜더링 한다. useState() 함수를 통해 state를 사용할 수 있다. useState는 배열을 반환하는데 [state, setFunction] 형태이다. useState의 인자는 state의 초기값이다. 여기서 setFunction은 state의 값을 변경하는 데 사용된다. import React, { useState } from 'react'; function ToggleComponent() { // useState를 사용하여 상태 초기화 const [state, setState] = useState('yes'); // 초기 상태는 'yes' // 상태..
2024.03.06
no image
백준 17298 오큰수 자바스크립트
입력 크기에서 힌트를 얻는 편이다. n이 1000000까지 들어온다는 것은 시간복잡도 O(n)으로 해결해야 1초로 끊을 수 있다는 뜻이다. 그러므로 배열을 선형으로 한번만 순회하면서 해결할 수 있는 방법을 생각해 보는 것이 좋겠다. 배열이 계속 내림차순 이라면 오큰수는 나올 수 없다. 반대로 배열이 오름차순이라면 맨 마지막 수를 제외한 모든 수는 자신의 앞에 수를 오큰수로 가질 것이다. 이를 토대로 생각해보면, 현재 내가 순회하는 숫자를 기준으로 내림 차순이 이어지면 오큰수는 알 수 없고, 나보다 큰 수가 나왔다면, 그 수는 여태 오큰수를 알 수 없던 모든 수의 오큰수 '후보'가 되는 것이다. 이제 해결법을 떠올릴 수 있다. 내림차순이라면 현재 순회하는 수의 정보를(값, 인덱스) 잠시 저장해두고, 오름차..
2024.03.04
no image
[OS4] Process, PCB
Process 프로세스는 실행중인 프로그램으로 볼 수 있다. 프로그램 그 자체는 Executable file이고 OS가 메모리에 올릴 때 프로세스로 추상화하여 메모리에 올리고 실행한다. 이 프로세스는 스케쥴링의 단위가 되고 서로 침범하지 못하는 독자적인 메모리 영역을 가지고 있다. Process 구성 관련 내용은 많으니 간단히 설명 Text : 코드 영역이라고도 하고 명령코드 자체를 저장하고 있다. 프로그램 카운터(PC)가 text segment에서 명령어를 실행하고 증가되는 식으로 코드를 실행한다. Data : 초기화 된 global, static 변수 bss : 초기화 값이 없는 global, static 변수 Heap : 동적으로 할당된 값이 저장되는 영역 Stack : 함수의 호출에 대한 스택프레..
2024.03.04
no image
X86과 ARM CISC 와RISC
나는 m1맥 유저다. 가끔 인텔 맥에서 컴파일 된 오브젝트 파일 때문에 빌드를 실패할 때가 있다. 어떻게 보면 당연한 것이라고 생각되지만, 어셈블리를 실행시킨다는 면에서는 다른 컴퓨터에서도 그냥 실행이 되야 하지 않나 싶다. 물론 그렇지 않다. cpu 인스트럭션이 다르기 때문에 어셈블리어도 다르게 나오기 때문이다. x86(AMD64, X86-64) x86 은 인텔에서 출시한 32비트 cpu아키텍처를 지칭한다. 현재 PC시장에서 많은 점유율을 차지하고 있다. 지금은 아무도 32비트 PC는 안쓰고, AMD에서 X86을 기반으로 설계한 64비트 CPU가 쓰인다. AMD64 혹은 X86-64라고 불린다. ARM ARM 홀딩스라는 회사가 있다. 쉽게 말하면 도면을 파는 회사이다. CPU를 직접 제조하진 않고 아키..
2024.02.25
CS
no image
백준 9934 완전 이진트리 node.js
접근법을 알면 쉽게 풀 수 있지만, 당연히도 접근법 생각하는 게 쉽지 않았던 문제이다. 입력으로 주어지는 배열은 트리를 중위순회한 값이라는 걸 알 수 있다. 이것이 매우 중요하다. 중위순회임임을 통해서 적절한 부분 배열로 나누었을 때 (자식 노드) - (부모 노드) - (자식 노드) 의 패턴이 유지된다는 것을 유추해야 한다. 여기가 어렵다. 입력의 인덱스 중앙값은 항상 트리의 루트 노드이다 즉 전체 배열 기준으로 양 부분 배열 사이의 노드가 부모 노드라는 뜻이다. 그리고 부모 노드 기준으로 양쪽 부분배열로 나누었을 때, 그 부분 배열 또한 부모 노드와 자식 노드를 포함하는 서브 트리임을 알 수 있다. 큰 문제를 같은 패턴의 작은 문제로 나눌 수 있고, 서로 독립적이다. 그러므로 재귀를 이용한 풀이를 생각..
2024.02.25
no image
[OS3] Monolithic Kernel, Micro Kernel, Hypervisor
Monolithic Kernel 커널의 모든 서비스가 같은 주소공간에 위치한다. Unix는 monolithic 구조이다. 프로세스의 공간은 (일반적으로) 하위 주소의 user space와 상위 주소의 kernel space로 구분할 수 있다. 프로세스의 모든 공간은 서로 독립적인 공간이다. 하지만 monolithic 구조에서 할당받은 커널 공간이 가리키는 주소는 물리적으로 같은 주소를 가리키게 된다. 이런 방식으로 서로 커널 서비스를 공유하게 되는 것이다. 이를 도식화하면 밑에 그림처런 나타낼 수 있다. monolithic 모두 같은 커널을 공유하기에 시스템콜과 커널 서비스 간의 오버헤드가 적다. 하지만 모든 서비스가 하나의 코드를 공유하는 만큼 일부분의 오류가 전체에 영향을 미칠 수 있다. 때문에 유지..
2024.02.23
no image
[OS3] Kernel Structure, Layering, Modularity, System call
Layering OS의 복잡도를 낮추기 위한 방안으로 시스템을 가상의 레이어로 나누는 방식이다. 각 레이어는 정의가 명확한 (well-defined) 함수들로 이뤄져있다. 하나의 레이어는 인접한 레이어와만 통신한다. 하나의 레이어를 건너뛸 수 없다는 특징 때문에 Overhead가 발생하기도 한다. Modularity 각 기능 단위로 모듈화를 시키고 OS를 구성하는 방식이다. 각 모듈은 내부적으로 layering 되어 있다. 초기 OS 구조 (MS-DOS) 모듈로 구분되어 있다는 개념보다는 하나의 큰 덩어리로 되어 있는 구조였다. 계층 구조를 건너뛰어서 User Application 또한 하드웨어 자원에 직접 접근할 수 있었다. 이는 유저모드와 커널모드의 구분이 없었다는 뜻이고 보안에 취약했다. MS-DO..
2024.02.23

Spring Boot 에서는 Validation을 통해 데이터의 유효성을 검증할 수 있다.

 

public class SignUpRequestDto {

	@NotBlank @Email
	private String email;

	@NotBlank @Size(min = 8, max = 20)
	private String password;

	@NotBlank
	private String nickname;

	@NotBlank @Pattern(regexp = "^[0-9]{11,13}$")
	private String telNumber;

	@NotBlank
	private String address;

	private String addressDetail;

	@NotNull @AssertTrue
	private Boolean agreedPersonal;
}

 

회원가입 요청에 대한 DTO 객체이다.

멤버 변수에 annotaion을 적용하여 유효성 검증을 지정할 수 있다.

  1. @NotBlank: 해당 필드가 null이 아니며, 공백을 제외한 길이가 0보다 커야 함을 나타냅니다. 주로 String 타입에 사용됩니다.
  2. @Email: 해당 필드가 이메일 주소 형식에 맞아야 함을 나타냅니다.
  3. @Size(min = x, max = y): 해당 필드의 크기가 지정된 범위 내에 있어야 함을 나타냅니다. min과 max를 통해 최소값과 최대값을 지정할 수 있습니다.
  4. @Pattern(regexp = "패턴"): 해당 필드가 주어진 정규 표현식에 일치해야 함을 나타냅니다. 여기서는 전화번호가 특정 패턴을 따르도록 강제하고 있습니다.
  5. @NotNull: 해당 필드가 null이 아니어야 함을 나타냅니다. 모든 객체 타입에 사용할 수 있습니다.
  6. @AssertTrue: 해당 필드의 값이 true이어야 함을 나타냅니다. 주로 boolean 타입에 사용되며, 약관 동의 여부 확인 등에 사용될 수 있습니다.
  1. @Min(value): 숫자 필드가 지정된 최소값 이상이어야 함을 나타냅니다.
  2. @Max(value): 숫자 필드가 지정된 최대값 이하이어야 함을 나타냅니다.
  3. @Digits(integer, fraction): 숫자 필드가 가질 수 있는 정수 부분과 소수 부분의 자릿수를 지정합니다.
  4. @DecimalMin(value): 숫자 필드가 지정된 값 이상이어야 함을 나타내며, 소수를 허용합니다.
  5. @DecimalMax(value): 숫자 필드가 지정된 값 이하이어야 함을 나타내며, 소수를 허용합니다.
  6. @Future@FutureOrPresent: 해당 필드의 날짜/시간이 현재보다 미래여야 함을 나타냅니다. @FutureOrPresent는 현재 시간도 유효함을 의미합니다.
  7. @Past@PastOrPresent: 해당 필드의 날짜/시간이 현재보다 과거여야 함을 나타냅니다. @PastOrPresent는 현재 시간도 유효함을 의미합니다.
  8. @Positive@PositiveOrZero: 숫자 필드가 각각 양수이거나 0을 포함한 양수여야 함을 나타냅니다.
  9. @Negative@NegativeOrZero: 숫자 필드가 각각 음수이거나 0을 포함한 음수여야 함을 나타냅니다.
  10. @NotEmpty: 컬렉션, 맵, 배열, 문자열 등이 비어있지 않아야 함을 나타냅니다.

 

RequestBody로 받은 DTO를 서버 사이드에서 @Valid 어노테이션을 통해 유효성을 검증한다

public class AuthController {
	
	private final AuthService authService;
	
	@PostMapping("/sign-up")
	public ResponseEntity<? super SignUpResponseDto> signUp(
		@RequestBody @Valid SignUpRequestDto requestBody
		) {
			ResponseEntity<? super SignUpResponseDto> response = authService.signUp(requestBody);
			return response;
	}	


	@PostMapping("/sign-in")
	public ResponseEntity<? super SignInResponseDto> signIn(
		@RequestBody @Valid SignInRequestDto requestBody
	){
		ResponseEntity<? super SignInResponseDto> response = authService.signIn(requestBody);
		return response;
	}
}

 

 

validation fail은 RestController Advice를 통해 예외 처리를 했다.

 

RestControllerAdvice

여런 컨트롤러에 대해서 전역적인 Exeption Handling을 제공한다.

ControllerAdvie + ResponseBody를 합쳐놓은 형태로, 에러 핸들링 ResponseBody로 핸들링에 대해서 객체로 리턴해줄 수 있다.

@RestControllerAdvice
public class BadRequestExceptionHandler {
	
	@ExceptionHandler({MethodArgumentNotValidException.class, HttpMessageNotReadableException.class})
	public ResponseEntity<ResponseDto> validationExceptionHandler(Exception exception) {
		return ResponseDto.validationFailed();
	}
}

 

state, useState

sumjo
|2024. 3. 6. 01:36

리액트의 state는 일반 자바스크립트 객체이고 랜더링을 유발하는 변수라고 볼 수 있다.

state 값이 변경되면 리액트는 해당 컴포넌트를 다시 랜더링 한다.

 

useState() 함수를 통해 state를 사용할 수 있다.

useState는 배열을 반환하는데 [state, setFunction] 형태이다. useState의 인자는 state의 초기값이다.

여기서 setFunction은 state의 값을 변경하는 데 사용된다.

import React, { useState } from 'react';

function ToggleComponent() {
  // useState를 사용하여 상태 초기화
  const [state, setState] = useState('yes'); // 초기 상태는 'yes'

  // 상태를 'no'로 업데이트하는 함수
  const changeStateCorrectly = () => {
    setState('no'); // 올바른 방법: setState를 사용하여 상태 업데이트
  };

  // 잘못된 방법으로 상태를 변경하려고 시도하는 함수 (실제로는 작동하지 않음)
  const changeStateIncorrectly = () => {
    state = 'no'; // 잘못된 방법: 직접 상태를 수정하려고 시도
    console.log(state); // 이 코드는 실제로는 React 상태 업데이트 원칙에 어긋나며, 실행되지 않음
  };

  return (
    <div>
      <p>Current state: {state}</p>
      <button onClick={changeStateCorrectly}>Change State Correctly</button>
      <button onClick={changeStateIncorrectly}>Change State Incorrectly (Won't Work)</button>
    </div>
  );
}

export default ToggleComponent;

 

위에 changeStateIncorrectly에서 state 값에 직접 할당을 하는 방식은 오류가 난다.

state는 직접 변경할 수 없고 useState에서 반환받은 setState를 통해 업데이트 해야 한다. state는 기본적으로 Immutable 객체이다.

 

리액트는 setState가 호출되면 랜더링큐에 컴포넌트가 들어가게 된다. 함수형 컴포넌트라면 함수의 반환값이 jsx 컴포넌트로 변화 되는 것이다. 리액트는 가상돔 트리를 만들어 이전의 트리와 차이를 비교한다.

여기서 state의 '차이'를 감지할 때 값이 아닌 콜스택의 주소값을 비교 즉 '얕은 비교'를 통해 변화를 인지한다.

때문에 state는 불변성을 지켜야 빠른 랜더링과 사이드 이펙트를 방지할 수 있다.

 

출처 : https://velog.io/@mollog/React%EC%97%90%EC%84%9C%EC%9D%98-%EA%B0%80%EC%83%81%EB%8F%94-%EA%B0%9C%EB%85%90

기존의 가상 돔과 변화된 버전의 가상돔을 비교하고 차이점을 commit한 후 다시 브라우저에 적용하는 모습이다.

 재귀적으로 만들어진 트리이기 때문에 하위 컴포넌트들도 자동으로 다시 호출되고 하위 컴포넌트도 변화를 감지한다.

 

import React, { useState } from 'react';

function CounterExample() {
  // useState를 사용하여 count 상태를 초기화
  const [count, setCount] = useState(0);

  function handleClick() {
    // 첫 번째 setCount는 현재 count 값(스냅샷)에 1을 더한다.
    setCount(count + 1);
    // 두 번째 setCount는 동일한 스냅샷(count의 현재 값)에 1을 더한다.
    setCount(count + 1);
    // 세 번째 setCount도 동일하게 처리된다.
    setCount(count + 1);
  }

  return (
    <div>
      <button onClick={handleClick}>Increase Count</button>
      <p>Count: {count}</p>
    </div>
  );
}

export default CounterExample;

 

state는 스냅샷으로 제공된다.

떄문에 setCount에서 count + 1을 하는 모든 count 는 스냅샷을 찍었던 시점의 값으로 저장된다.

리액트는 batcing을 통해 state를 변경하는 작업을 일괄적으로 처리한다.

 

입력 크기에서 힌트를 얻는 편이다.

n이 1000000까지 들어온다는 것은 시간복잡도 O(n)으로 해결해야 1초로 끊을 수 있다는 뜻이다.

그러므로 배열을 선형으로 한번만 순회하면서 해결할 수 있는 방법을 생각해 보는 것이 좋겠다.

 

 

배열이 계속 내림차순 이라면 오큰수는 나올 수 없다. 반대로 배열이 오름차순이라면 맨 마지막 수를 제외한 모든 수는 자신의 앞에 수를 오큰수로 가질 것이다.

이를 토대로 생각해보면, 현재 내가 순회하는 숫자를 기준으로 내림 차순이 이어지면 오큰수는 알 수 없고,

나보다 큰 수가 나왔다면, 그 수는 여태 오큰수를 알 수 없던 모든 수의 오큰수 '후보'가 되는 것이다.

 

이제 해결법을 떠올릴 수 있다. 내림차순이라면 현재 순회하는 수의 정보를(값, 인덱스) 잠시 저장해두고, 오름차순일 때는 저장된 값들 중에서 오큰수의 조건을 만족하는 값들을 갱신해주면 되는 것이다.

여기서 쉽게 떠올릴 수 있는 구조가 바로 스택 이다. 근데 난 문제를 풀 때는 생각이 안났고 최소힙이 생각났다.

최소힙에서 조건에 맞을때마다 요소룰 빼면서 오큰수를 갱신해주면 된다.

스택도 이와 동일하게 생각하면 된다.

 

const readline = require('readline');
const rl = readline.createInterface({
	input:process.stdin,
	output:process.stdout
})

const input = [];
rl.on('line', (line)=>{
	input.push(line);
}).on('close', () =>{

	const n = Number(input[0]);
	const arr = input[1].split(' ').map(Number);
	const ans = Array(n).fill(0);
	const q = [];


	for(let i = 0; i < n; i++){
		const j = i+1;
		if(j === n)
			break;
		if(arr[j] <= arr[i]){
			q.push([i, arr[i]]);
		}
		else{
			ans[i] = arr[j];
			q.sort((a,b) => a[1] - b[1])
			while(q.length > 0){
				if(q[0][1] < arr[j]){
					ans[q[0][0]] = arr[j];
					q.shift();
				}
				else
					break;
			}

		}
	}

	for(let i = 0; i < ans.length; i++){
		if(ans[i] === 0)
			ans[i] = -1;
	}


	console.log(ans.join(' '));

})

 

이 코드는 sort연산에서 O(nlogn)이 나오기 때문에 시간초과가 뜬다.

그래서 우선순위 큐를 직접 구현하면 통과가 된다. (코드가 길어서 올리진 않았다) 물론 오래 걸린다. 거의 꼴지 수준이다. 요소를 뺄 때 log(n)이 걸려서 그럴 것이다. 아마 몇없는 우선순위 큐로 통과한 사람중 하나인 것 같다. 그러니까 O(1)의 스택을 사용하도록 하자.

[OS4] Process, PCB

sumjo
|2024. 3. 4. 00:35

Process

프로세스는 실행중인 프로그램으로 볼 수 있다. 프로그램 그 자체는 Executable file이고 OS가 메모리에 올릴 때 프로세스로 추상화하여 메모리에 올리고 실행한다. 이 프로세스는 스케쥴링의 단위가 되고 서로 침범하지 못하는 독자적인 메모리 영역을 가지고 있다.

 

 

일반적인 상황에서 서로의 영역을 침범하지 않도록 운영체제가 관리한다

 

Process 구성

관련 내용은 많으니 간단히 설명

 

Text  : 코드 영역이라고도 하고 명령코드 자체를 저장하고 있다. 프로그램 카운터(PC)가 text segment에서 명령어를 실행하고 증가되는 식으로 코드를 실행한다.

Data : 초기화 된 global, static 변수

bss : 초기화 값이 없는 global, static 변수

Heap : 동적으로 할당된 값이 저장되는 영역

Stack : 함수의 호출에 대한 스택프레임과 그와 관련된 지역변수를 저장, 말그대로 스택이라 함수 호출시 스택처럼 쌓이는 방식

그 유명한 스택 오버플로우가 났다고 하면 Stack 공간에 데이터가 쌓이다가 터진것임. Heap에 비해 상대적으로 작다.

예시로 보면 이런 느낌이다.

 

Process State

프로세스는 라이프 사이클 동안 여러 상태를 가지게 된다.

New , Created: PCB와 함께 프로세스가 생성된 상태, 바로 실행되는 것은 아님

Ready : 프로세스가 CPU에 할당되기 위해 대기하는 상태

Running : CPU에 의해 프로세스가 실행되고 있는 상태

Waiting, Blocked : 프로세스가 실행 되다가 I/O 처리 등 바로 완료될 수 없는 작업을 위해 잠시 CPU를 반납하고 대기하는 상태

Terminated: 프로세스가 종료 되기 직전에 잠시 거치는 상태

 

Process Controll Block(PCB)

운영 체제에서 프로세스 관리에 필요한 메타데이터를 저장하는 자료 구조.

위에서 말한 프로세스의 상태(State)를 PCB에서 관리한다.

그 외에 프로그램이 다음으로 실행시킬 주소값을 담고있는 프로그램 카운터, CPU 레지스터가 가지고 있는 값,

스케쥴링 우선순위 등에 대한 정보, 열고있는 파일 등등의 정보를 가지고 있다.

 

이 PCB는 컨텍스트 스위칭에 중요한 역할을 한다.

프로세스가 Ready상태에서 다시 Running 될 때 프로세스의 PCB를 통해

프로세스가 이전까지 실행하던 위치(Program Counter), 스택 프레임, 레지스터 값등을 참조하여

이전의 실행 문맥(Context)부터 이어서 실행할 수 있는 것이다.

X86과 ARM CISC 와RISC

sumjo
|2024. 2. 25. 07:21

나는 m1맥 유저다. 

가끔 인텔 맥에서 컴파일 된 오브젝트 파일 때문에 빌드를 실패할 때가 있다.

어떻게 보면 당연한 것이라고 생각되지만, 어셈블리를 실행시킨다는 면에서는 다른 컴퓨터에서도 그냥 실행이 되야 하지 않나 싶다.

물론 그렇지 않다. cpu 인스트럭션이 다르기 때문에 어셈블리어도 다르게 나오기 때문이다.

 

 

x86(AMD64, X86-64)

x86 은 인텔에서 출시한 32비트 cpu아키텍처를 지칭한다. 현재 PC시장에서 많은 점유율을 차지하고 있다.

지금은 아무도 32비트 PC는 안쓰고, AMD에서 X86을 기반으로 설계한 64비트 CPU가 쓰인다. AMD64 혹은 X86-64라고 불린다.

 

ARM

ARM 홀딩스라는 회사가 있다. 쉽게 말하면 도면을 파는 회사이다. CPU를 직접 제조하진 않고 아키텍쳐를 설계해서 기업에 제공하고 로열티나 라이센스를 받는게 주업이다.

그리고 ARM은 이 ARM 홀딩스에서 설계한 명령어 집합과 아키텍쳐를 총칭하는 것이다. 

 

 

CISC (Complex Instruction Set Computer)

복잡한 명령어 셋을 가지고 있는 프로세서이다. 대표적으로 X86-64가 채택하고 있다.

복잡한 명령어 라는 의미는 하나의 명령어가 할 수 있는 일의 양이 RISC에 대비해서 많다는 의미이다. 그만큼 필요한 명령어를 모두 갖추고 있다는 컨셉이다.

명령어마다 길이가 다르고, 복잡한 명령어를 사용하는 만큼 전력 소모도 크다.

 

RISC (Reduced Instruction Set Computer)

적은 수의 명령어를 가지고 있는(빈도가 높은 20%) 프로세서 구조이다. 모든 동작이 몇개의 핵심 명령어를 통해서 작동된다는 것을 기반으로 한다. ARM이 RISC구조를 채택하고 있다.

모두 고정된 명령어(1 word)단위를 가지고 있다. 즉 명령어의 길이가 같고 짧다. 때문에 코어 간 병렬처리가 용이하고 전력 소모도 적다.

명령어가 짧고 최소한의 일만 수행하기 때문에 명령어의 사용이 더 많고 어셈블리로 바꾸었을 때 용량이 더 크다.

 

 

간단한 예시

두 값을 곱한다고 하자. 두 값의 주소는 0x11 0x22 라고 하자.

컴퓨터는 메모리에 접근한다. 두 값을 더한다는 것은 특정 메모리 위치의 두 값을 레지스터로 가져온 후 연산을 수행하고 다시 메모리에 적는 것이다. 

RISC 로 따지면

 

LOAD  A 0x11

LOAD  B 0x22

PROD A B

STORE 0x11 A

 

각가 A,B 레지스터에 값을 가져오고(LOAD) 연산을 수행한 뒤(PROD) 다시 0x11의 주소에 값을 저장(STORE)한다.

 

CISC 는 MULT(Multiple)라는 명령어가 이미 구현되어 있는 것이다. 물론 내부적으로 저 과정을 거치긴 하지만 하나의 명령어 셋이다.

하지만 속도적으로는 RISC의 저 명령들은 MULT에 근접하거나 그 이상일 수 있다.. 하나의 명령어가 최소 단위의 사이클을 가지기 때문이다.

 

CISC 접근은 명령어당 사이클 수의 희생하면서 프로그램당 명령어 수를 최소화 시키려고 한다. RISC는 반대로 프로그램당 명령어 수 증가를 감수하고 명령어 당 사이클 수를 최소화한다.

 

비유하자면 CISC는 고수준 언어 같은 것이다.

메모리 할당, 가비지 컬렉터 등 필요한 기능이 전부 마련되어 있어서 malloc도 free도 할 필요가 없는.

대신 느리고 리소스를 많이 잡아먹는다. RISC는 저수준이지만 최적화에 용이하다.

재밌는 비유다.

 

출처 : https://blog.naver.com/ytghandsome/100065632980

 

 

흥미로운 내용

CISC는 병렬 처리가 쉽지 않다. 명령어의 길이가 다르기 때문에 각 코어가 일을 시작하고 끊는 시점이 제각각이기 때문에 비효율이 많이 발생하게 된다. 그래서 죽어라 최적화 시키지 않는 이상은 깡클럭 즉 싱글코어 성능이 중요하다. 한번에 여러 일을 잘 못하니까 그냥 한놈이 엄청 빠르게 해줘야 되는 것이다.

RISC는 명령어 길이가 같으니 병렬  처리에 용이

 

또 M1은 왜 배터리가 오래 갈까. 한번에 복잡한 계산을 하는 것보다 단순한 계산을 여러번 하는 것이 전력 소모가 덜하다고 한다. RISC의 특징이다. 

 

지금은 RISC와 CISC각자 서로의 장점을 도입하기 때문에 경계는 흐릿해지고 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

접근법을 알면 쉽게 풀 수 있지만, 당연히도 접근법 생각하는 게 쉽지 않았던 문제이다.

입력으로 주어지는 배열은 트리를 중위순회한 값이라는 걸 알 수 있다. 이것이 매우 중요하다.

중위순회임임을 통해서 적절한 부분 배열로 나누었을 때 (자식 노드) - (부모 노드) - (자식 노드) 의  패턴이 유지된다는 것을 유추해야 한다. 여기가 어렵다.

 

입력의 인덱스 중앙값은 항상 트리의 루트 노드이다 즉 전체 배열 기준으로 양 부분 배열 사이의 노드가 부모 노드라는 뜻이다.

그리고 부모 노드 기준으로 양쪽 부분배열로 나누었을 때, 그 부분 배열 또한 부모 노드와 자식 노드를 포함하는 서브 트리임을 알 수 있다.

큰 문제를 같은 패턴의 작은 문제로 나눌 수 있고, 서로 독립적이다. 그러므로 재귀를 이용한 풀이를 생각해낼 수 있다.

 

부모 노드는 항상 부분 배열의 중앙으로 확정이기 때문에, 부모 노드를 해당 높이의 트리(배열)에 넣은 후 이후 부모 노드 기준으로 다시 양쪽 배열로 분할한 후 재귀 함수를 호출한다.

let readline = require("readline");

let rl = readline.createInterface({
	input: process.stdin,
	output: process.stdout,
});

let input = [];

rl.on("line", (line)=> {
	input.push(line);
}).on("close", () => {

	const k = Number(input[0]);
	const arr = input[1].split(' ').map(Number);

	const ans = Array(k+1);
	for(let i = 0; i <= k; i++)
		ans[i] = '';


	const make = (arr, depth) => {

		if(depth > k || arr.length === 0)
			return;

		const mid = Math.floor(arr.length / 2);
		if(ans[depth].length === 0)
			ans[depth] += arr[mid].toString();
		else
		ans[depth] += ' ' + arr[mid].toString();
		const leftarr = arr.slice(0, mid);
		const rightarr = arr.slice(mid+1, arr.length);

		make(leftarr, depth + 1);
		make(rightarr, depth + 1);
	}

	make(arr, 1);

	console.log(ans.slice(1,ans.length).join('\n'))
})

 

 

 

Monolithic Kernel

커널의 모든 서비스가 같은 주소공간에 위치한다. Unix는 monolithic 구조이다.

프로세스의 공간은 (일반적으로) 하위 주소의 user space와 상위 주소의 kernel space로 구분할 수 있다.

프로세스의 모든 공간은 서로 독립적인 공간이다. 하지만 monolithic 구조에서 할당받은 커널 공간이 가리키는 주소는 물리적으로 같은 주소를 가리키게 된다.

이런 방식으로 서로 커널 서비스를 공유하게 되는 것이다. 이를 도식화하면 밑에 그림처런 나타낼 수 있다.

 

monolithic 모두 같은 커널을 공유하기에 시스템콜과 커널 서비스 간의 오버헤드가 적다.

하지만 모든 서비스가 하나의 코드를 공유하는 만큼 일부분의 오류가 전체에 영향을 미칠 수 있다. 때문에 유지보수가 어렵다

 

Micro Kernel

기능별로 마이크로 서비스로 개발하여 많은 서비스를 커널 모드가 아닌 유저 모드에서 처리할 수 있는 커널 시스템이다.

커널에는 필수적인 최소의 기능들만 남겨둔다. 나머지 분리된 모듈을 서버라고 하고, 서버들은 하나의 프로세스로 유저 모드에서 구동된다.

monolithic에 비해 커널이 가볍고, 의존도가 확실히 내려가기 때문에 유지 보수에 용이하다.

하지만 서버 간 통신이 필요하기 때문에 monolithic kernel 보다 낮은 성능을 보인다.

 

Hypervisor

d가상화된 컴퓨터 하드웨어 자원을 제공하기 위한 관리 계층

단일한 컴퓨터에서 여러 가상 머신을 운용할 수 있게 물리적인 컴퓨팅 리소스 자원을 분배하는 소프트웨어 이다.

게스트 OS와 하드웨어 사이에 위치한다. 각 게스트 OS는 서로의 존재를 알지 못한다.

하이퍼바이저를 통해 내 컴퓨터와 다른 인스트럭션 셋을 가진, 이를테면 RISC 명령어 set을 가진 컴퓨터에서 CISC 인스트럭션을 실행할 수도 있다.

 

 

 

 

Layering

OS의 복잡도를 낮추기 위한 방안으로 시스템을 가상의 레이어로 나누는 방식이다.

각 레이어는 정의가 명확한 (well-defined) 함수들로 이뤄져있다.

하나의 레이어는 인접한 레이어와만 통신한다.

하나의 레이어를 건너뛸 수 없다는 특징 때문에 Overhead가 발생하기도 한다.

 

 

Modularity

각 기능 단위로 모듈화를 시키고 OS를 구성하는 방식이다.

각 모듈은 내부적으로 layering 되어 있다.

 

초기 OS 구조 (MS-DOS)

모듈로 구분되어 있다는 개념보다는

하나의 큰 덩어리로 되어 있는 구조였다.

계층 구조를 건너뛰어서 User Application 또한 하드웨어 자원에 직접 접근할 수 있었다.

이는 유저모드와 커널모드의 구분이 없었다는 뜻이고 보안에 취약했다.

출처 : https://ddongwon.tistory.com/10

MS-DOS에서 프로그램이 돌아갈떄와 돌아가지 않을 때 메모리의 그림이다.

멀티 프로그래밍에 대한 개념이 없었기 때문에 프로세스 개념도 없고

프로그램이 돌지 않을 때는 명령어 해석기가 올라와 있고

프로그램이 실행되면 필요한 메모리를 모두 할당해주는 방식이다.

 

커널 내의 모듈들

 

User Mode Kernel Mode

위에 예시로 말한 MS-DOS의 구조처럼, 하드웨어 핵심 자원에 접근이 자유롭다면 보안적으로 취약할 수 밖에 없다.

때문에 Mode를 나누어 각각의 접근 권한을 달리한다.

Mode는 소프트웨어가 아닌 하드웨어 선에서 제어하게 된다.

Mode bit를 두어서 0일때는 운영체제가 cpu를 제어하여 여러 특권명령을 실행할 수 있는 Kernel Mode 이고 1이면 사용자 프로그램이 cpu를 사용하고 사용자 영역 안의 명령어만 실행 가능한 User Mode 이다

시스템 콜

User Mode 에서 Kernel Mode 로 진입하기 위한 통로이다. 커널에서 제공하는 하드웨어 접근을 위한 Protected된 인터페이스이다.

유닉스 계열 커널에서 제공하는 fork(), open() 등이 예시이다. 시스템콜이 호출되면 커널모드로 전환 되고 이를 통해 프로세스는 하드웨어에 직접 접근할 수 있는 것이다.

 

 

시스템콜이 호출되면 Mode의 전환이 일어나고 시스템콜은 정의되어있는 매크로로 변환된다.

매크로를 통해 system call table에 맵핑되어 있는 시스템콜 수행된다.

application이 시스템콜 인터페이스를 통해 하드웨어에 접근하는 도식