728x90

전역 값

회사 프로젝트에서 resources 디렉토리에 있는 설정파일은 application.properties를 사용한다.

그래서 여기에 값을 할당해주고 @Value를 가져다가 사용한다.

properties의 값을 가져올때는 @Value("${properties키 값}")으로 불러오기 때문에

이번에 내가 진행하게된 프로젝트 설정파일 application.yaml에도 똑같이 @Value가 적용될줄 알았다.

근데 적용하려고 보니까 에러가 나는것이다.

에러메세지부터 바로 보자.

에러메세지

Could not resolve placeholder 'application.open-api.adminKey' in value "${application.open-api.adminKey}"

라는 에러가 발생했다.

설정을 전부 그대로 두고 다시 구현해보니까 그냥 성공하게 되는 이유는 무엇일까 🤔

추후에 다시 알아봐야 할 것 같다.

멍청하기는 아래의 해결법에서 yaml의 키를 보면 admin-key인데

위에 오류메세지에서는 adminKey로 매핑을 시켰었다.

정말 한심하기 짝이없다. 👿👿👿👿👿👿

아무튼 이런 설정 방법도 있구나 라는것을 알아두면 되겠다.

해결법

Baeldung 설정

벨덩 문서에 가보면 yaml파일 설정에 대한 것을 설명해주었다.

이걸보고 나는 키값을 할당해주려고 아래와 같이 구현하였다.

application.yaml

open-api:
  admin-key: 키값
  authorization: 인증코드

YamlConfiguration.java

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "open-api") // 기본이 되는 뿌리 키값 여기서는 open-api이다.
@Getter
@Setter //setter가 없으면 Setter가 없다고 에러를 뱉어준다.
public class YamlConfiguration {
    private String adminKey;
    private String authorization;
}

여기서 @ConfigurationProperties 라는 어노테이션을 사용하려면 gradle에서

annotationProcessor에 다음을 추가해주어야한다.

annotationProcessor (
    // ...
            'org.springframework.boot:spring-boot-configuration-processor'
    )

그리고 사용하는 객체에서는 의존성 주입을 받아서 사용하면 되겠다.

public class Foo {

    private final YamlConfiguration yamlConfiguration;

    public Foo(YamlConfiguration yamlConfiguration) {
        this.yamlConfiguration = yamlConfiguration;
    }

    public void test() {
        yamlConfiguration.getAdminKey();
        yamlConfiguration.getAuthorization();
    }

}

@ValueConfiguration을 설정하는방법으로 어떤게 더 편한지 가독성이 좋은지 잘 모르겠다.

상황에 따라 편한걸 사용하는게 맞는지 이거는 조금 더 협업을 해보면 답이 찾아지지 않을까 싶다.

728x90

'디버깅' 카테고리의 다른 글

Jenkins 에러  (0) 2022.08.11
AWS SNS 토큰 에러  (0) 2022.08.10
AbstractMessageConverter  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
728x90

HandlerMethodArgumentResolver

HandlerMethodArgumentResolver에 정의되어있는 자바독을 읽으면

주어진 요청의 컨텍스트에서 메소드 매개변수를 인수 값으로 해석하기 위한 전략 인터페이스라고 설명되어 있다.

HandlerMethodArgumentResolver에는

스크린샷 2021-11-29 오후 10 09 59

이렇게 두개의 메소드가 있는데 supportsParameter()로 메소드의 매개변수를 처리할 수 있는지 여부를 판단한다.

@RequestBody

@RequestBody 어노테이션을 읽으려면

HandlerMethodArgumentResolver
AbstractMessageConverterMethodArgumentResolver
AbstractMessageConverterMethodProcessor
RequestResponseBodyMethodProcessor

이순서로 확장되어있는 Resolver를 찾아보면 된다.

저번 포스팅에서 봤던 ArgumentResolver들을 포함한 RequestMappingHandlerAdapter 에서는

HandlerMethodArgumentResolverComposite를 주입받게 된다.

HandlerMethodArgumentResolverComposite에서도

맞는 ArgumentResolver를 찾아서 동작하게 하려고 하는데 27개의 리졸버중에

RequestResponseBodyMethodProcessor@RequestBody를 처리해준다.

스크린샷 2021-11-29 오후 10 24 32
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestBody.class);
    }
}

그리고 이제 이 객체를 바인딩 해야한다.

MessageConverter

HandlerMethodArgumentResolverComposite에서 메세지 컨버터를 10개중에 또 찾아내야 한다.

그거는 아래에서 설명하겠다.

스크린샷 2021-11-29 오후 10 31 17

HttpInputMessage에서 들어온 키값과 @RequestBody가 붙은 객체 인자들 값을 서로 비교해서

맞으면 매핑을 시켜주는 것 같다.

AbstractMessageConverterMethodArgumentResolver

AbstractMessageConverterMethodArgumentResolver에서 MediaType

메세지 컨버터를 맞는걸 찾는데 걸리는 컨버터는 바로 MappingJackson2HttpMessageConverter이다.

예상은 하고 있었지만 왜 저 클래스가 GenericHttpMessageConverter 인지는 아직 잘 모르겠다.

메소드중 위 기능을 하는 일부를 가져와보았다.

스크린샷 2021-11-29 오후 10 46 47

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
  @Nullable
  protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    MediaType contentType;
    boolean noContentType = false;
    try {
      contentType = inputMessage.getHeaders().getContentType();
    }
    catch (InvalidMediaTypeException ex) {
      throw new HttpMediaTypeNotSupportedException(ex.getMessage());
    }
    if (contentType == null) {
      noContentType = true;
      contentType = MediaType.APPLICATION_OCTET_STREAM;
    }

    Class<?> contextClass = parameter.getContainingClass();
    Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
    if (targetClass == null) {
      ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
      targetClass = (Class<T>) resolvableType.resolve();
    }

    HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
    Object body = NO_VALUE;

    EmptyBodyCheckingHttpInputMessage message;
    try {
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

      for (HttpMessageConverter<?> converter : this.messageConverters) { //여기에 아까 말했던 Converter가 10개 들어오게 된다.
        Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
        GenericHttpMessageConverter<?> genericConverter =
            (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
        if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
            (targetClass != null && converter.canRead(targetClass, contentType))) {
          if (message.hasBody()) { //여기가 참 조건이 성립하려면 위에서 GenericHttpMessageConverter 클래스 유형이어야 한다.
            HttpInputMessage msgToUse = //여기는 메소드 이름을 보면 body를 읽기 전,후 로 나뉘어 있어 aop동작을 하는것으로 짐작된다.
                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
            body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
            body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
          }
          else {
            body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
          }
          break;
        }
      }
    }
    catch (IOException ex) {
      throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
    }

    if (body == NO_VALUE) {
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
          (noContentType && !message.hasBody())) {
        return null;
      }
      throw new HttpMediaTypeNotSupportedException(contentType,
          getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
    }

    MediaType selectedContentType = contentType;
    Object theBody = body;
    LogFormatUtils.traceDebug(logger, traceOn -> {
      String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
      return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
    });

    return body;
  }
}

이렇게 해서 커맨드 객체 @RequestBody가 붙은 HelloForm유형에 맞는

데이터들을 파싱해주고 처리를 해줄 수 있게 된다.

번외로 AbstractMessageConverterMethodProcessor의 구현체는

RequestResponseBodyMethodProcessor, HttpEntityMethodProcessor 이므로

새롭게 알게된 사실인데 @RequestBody를 붙이고 싶지 않다면 HttpEntity<T>RequestEntity<T>로 매개변수를 받아주면

HttpEntityMethodProcessor가 동작하고, 가공이 조금 덜 되었지만 그래도 그안에 커맨드 객체까지 담아오는 body 데이터를 받을 수 있다.

정리

내 수준으로는 아직 이것밖에 이해 못했지만 그래도 얼추 디버깅으로 바짝 쫓아갈 수는 있다고 생각한다.

예전엔 이걸 봐도 어떻게 돌아가는지 무지성으로 넘기기만 했지, 자세하게 들여다 볼 실력도 안됐었다.

근데 지금은 천천히 늦지만서도 찍어보면서 어떻게 흐름이 진행되는지는 감을 익히는 것 같다.

이 부분도 자바 공부를 더 하다보면 깊게 알 수 있게 되지 않을까 싶다.

HelloController.java

@RestController
@RequestMapping("/api/v1")
public class HelloController {

    @PostMapping("/hello")
    public ResponseEntity<?> hello(@RequestBody HelloForm helloForm) {
        return ResponseEntity.ok(Map.of("message", "success","data", helloForm));
    }
}

HelloControllerTest.java

@WebMvcTest(HelloController.class)
class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void test() throws Exception {
        String ss = "{\"message\": \"hello\", \"name\": \"lsj\"}";
        mockMvc.perform(post("/api/v1/hello")
                .content(ss)
                .contentType(MediaType.APPLICATION_JSON_VALUE))
            .andDo(print())
            .andExpect(status().isOk());
    }
}

HelloForm.java

@Getter //없으면 406에러를 발생한다. 깂을 못읽어 주입을 못해주는것 같다.
@NoArgsConstructor
public class HelloForm {

    private String message;
    private String name;

    //없으면 커맨드 객체 주입 안됨
    public HelloForm(String message, String name) {
        this.message = message;
        this.name = name;
    }

}
728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
RequestMapping 동작  (0) 2022.08.09
728x90

@RequestParam을 처리해주는 아규먼트 리졸버 = RequestParamMethodArgumentResolver

RequestParamMethodArgumentResolver는 AbstractNamedValueMethodArgumentResolver를 상속한 콘크리트 클래스고, resolveArgument를 오버라이딩 하지 않았기 때문에 AbstractNamedValueMethodArgumentResolver.resolveArgument가 호출된다.

흐름도

스크린샷 2021-11-25 오후 9 14 30

일단 흐름도는 이러하다.

어제 살펴봤던 내용은 DispatcherServlet 이전의 처리내용이었다.

스프링 MVC에 대해서 교환대라고 할 수 있는 DispatcherServlet 이 클래스가

HandlerAdapter 등.. 조건에 부합하는 객체들을 찾아서 전달을 다 해준다.

@RequestParam 이 있을 때는 당연히 매핑이 되어서 가져올것은 알았지만

없을 때 생략해도 가져오는 이것은 동작이 어떻게 되는지 궁금했다.

코드부터 보자.

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(String id, String name) {
        return id + name;
    }

    @GetMapping("/hello2")
    public String hello2(@RequestParam String id, String name) {
        return id + name;
    }

}

이러한 두개의 예시 hello, hello2를 만들었고 테스트 코드로 디버깅하는게 더 좋을것 같아서

테스트 코드로 디버깅을 진행했다.

@WebMvcTest(HelloController.class)
@AutoConfigureMockMvc
class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void hello() throws Exception {
        mockMvc.perform(get("/hello?id=lsj&name=홍길동"))
            .andDo(print())
            .andExpect(status().isOk());
    }

}

일단 돌리게 되었을 때 DispatcherServletRequestMappingHandlerAdapter를 호출하게 된다.

여기서 요청 객체를 처리하는줄 알았었는데

ArgumentResolver들 중에 해당 객체를 처리할 수 있는 Resolver 클래스를 찾는다.

스크린샷 2021-11-25 오후 9 25 03

RequestMappingHandlerAdapter의 일부 메소드인데 이 클래스가

부름을 받으면 바로 Resolver들을 추가해주는데

여기서 유심히 봐야하는 부분이 디버그로 파란줄 쳐진 부분과

//catch-all아래 2번째줄 이 두줄을 유심히 봐야하는데

여기서 내가 느낀것은 useDefaultResolution이 옵션이라고 생각하고 넘어갔다.

스크린샷 2021-11-25 오후 9 28 58

HandlerMethodArgumentResolverComposite

다음은 저 어댑터들을 가지고서 넘어온 파라미터를 처리해줄 수 있는 Resolver들을 찾는데

스크린샷 2021-11-25 오후 9 41 16

그것은 이 HandlerMethodArgumentResolverComposite에서 for문으로 찾아주게 되어있다 ❗️❗️❗️❗️

RequestParamMethodArgumentResolver

여기서 체크하는 로직이 위 이미지 RequestParamMethodArgumentResolver 이다.

보면 파라미터 어노테이션을 갖고 있는지 여부, 또는 @RequestPart를 갖고있는지 등등

분기로 판단해서 객체를 처리하려고 한다.

여기서 아까 보고 넘어갔다했던 useDefaultResolution 애가 분기문에 this.useDefaultResolution이 보이는데

이게 true라면 현재 가지고있는 매개변수의 타입대로 따라가서 값을 매핑시켜준다.

스크린샷 2021-11-25 오후 9 35 30

0번째 인자는 false, 25번째 인자는 true를 갖고있다.

정리를 해보자면

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
상태값이 false이면 @RequestParam이 있는 경우 매개변수 생성

resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
상태값이 true이면 @RequestParam이 없는 경우 매개변수 생성을 하는 것

그래서 둘다 있으나 없으나 생성을 해준다.

근데 이제는 성능은 어떤게 좋냐고 묻는다면 명시적으로 @RequestParam을 붙인 객체는 0번째에서 바로

찾아서 매핑이 될것이다.

반대로 명시적으로 붙이지 않았다면?

매번 25번째에 있는 Resolver를 통해서 매핑시키게 될 것이다. 이게 단순 한개라면 모르겠지만,

여러 사용자 + 여러 스레드 + 파라미터의 갯수 세개의 조건이 셋중에 하나 또는 전부가 많아진다면

성능은 안좋아질게 훤히 보인다.

그래서 안붙여도 되지만 성능을 최적화 하려면 명시적으로 @RequestParam을 붙여주는것이 좋다.

정리

테코톡에서 디버그를 본 후에 깊게 한번 들어와서 공부를 해보려고 어제부터 탐색을 했다.

깊게 이렇게 들어와서 하나씩 보는것이 소스분석에도 도움이 되고 더 나아가서는

회사코드를 인계받을 때에도 이렇게 분석을 하면 핵심 로직을 빠르게 파악할 수 있을 것 같다.

그러면서 동시에 이렇게 정리까지 하니 머릿속에 많이 남아서 지식으로 가져가는 것도 좋은것 같다.

공부를 이렇게 했어야 됐는데 너무 늦은건가 싶기도 하지만 꾸준한게 답인것 같다. 🔥🔥🔥

728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
AbstractMessageConverter  (0) 2022.08.09
RequestMapping 동작  (0) 2022.08.09
728x90

얼마만의 포스팅인지 모르겠다.

일단 바로 스타트 ❗❗❗

깃허브를 원래 올리는게 맞지만 회사 코드라서 따로 올리지는 못한다. 😥😥😥

테스트

@GetMapping에 대해서 어떻게 돌아가는지 궁금해서 무작정 실행을 시켜봤다.

image

지금 보이는 이미지는

톰캣에서 HTTP 메세지를 받아오는 구간이다.

그러니까 DispatcherServlet전에 수행되는 구간이다.

어떻게 저걸 담고있냐는

localhost:8081/swagger-ui.html 이라는 곳에서

크롬브라우저를 이용한 HTTP 통신을 하려고한다 라는 헤더를 추출한것이다.

image

여기서 api를 호출하고 GET방식으로 조회하는것 까지 확인했다.

오해했던 부분

처음에 나는 @GetMapping이 GET메소드를 만들어준다?

라고 생각했다.

HTTP 완벽 가이드를 읽으면서도 멍청하게 생각을 했다.

나는 바보다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그런게 아니라 HTTP 메소드에서 헤더를 보고

이러이러한 요청이 있다. 부터 시작하는 것이 웹의 동작일텐데 간과하고 있었다.

과정

이렇게 쭉 지나오면 이제 FrameworkServlet을 만나게 된다.

image

이 클래스는 추상클래스로 되어있고

이걸 구현한게 바로 DispatcherServlet이다.

image

프레임워크 서블릿의 이 메소드를 지나면서

디스패처 서블릿이 이제 해당 요청에 대한 메소드를 탐색한다.

이 때 탐색하라고 핸들러에게 넘기는데

image

그 핸들러가 바로 RequestMappingHandlerAdapter 클래스이다.

얘가 @RequestMapping에 대한 모든 요청을 받아서 매칭을 해주고

핸들러 메소드들을 쭉 지나서 마지막에는 내가 구현한 API쪽으로 넘어오게 된다.

그렇게해서 해당하는 데이터들을 가져와서 클라이언트쪽으로 가공된 알맞는 데이터들을 보내주게 된다.

정리

이렇게 깊게 찾아보는게 기억에도 훨씬 잘남고 개념 확립이 착착 되는것 같아 좋다.

문득 낮에 보던 우아한 테코톡 디버깅을 보고 그리고 순간 궁금했어서 이렇게 찾아보게 된것 같다.

암만 주저리주저리 읊고 해봤자 모르겠고

그냥 코드를 보는게 답이라고 생각한다.

728x90

'디버깅' 카테고리의 다른 글

AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09
AbstractMessageConverter  (0) 2022.08.09
RequestParamMethodArgumentResolver  (0) 2022.08.09
728x90

OpenSSH

이 글을 쓰게된건 일단 회사에서 여유시간이 생기게 되면 공부하는 시간을 가져도 된다고 하셨었다.

그래서 이참에 사이드 프로젝트 하던것을 좀 ec2 서버에 올려놓으면서 RDS설정도 해야겠다 싶었다.

근데 키가 없으니까 또 접속하기가 매우 불편했다.

왜냐면, 웹으로 접속하는 법을 썼어야 했으니까.

키 파일이 없는데 언제 어디서나 접속하게끔 하고 싶었고

그렇게 적용하게된 OpenSSH이다.

설치

일단 시작은 다음과 같다.

sudo apt update
sudo apt-get install openssh-server

먼저 패키지 시스템을 업데이트 해주고, openssh-server를 설치해준다.

ssh를 설치하게 되면 자동으로 실행된다.

그리고 나서 현재 상태를 조회할 수 있다.

sudo systemctl status ssh
스크린샷 2021-11-11 오후 8 51 12

이렇게 상태를 보고 동작중인지 확인이 되었다.

만약에 동작중이 아니라면

sudo systemctl enable ssh
sudo systemctl stop ssh
sudo systemctl start ssh

sudo systemctl restart ssh

stop, start를 사용하던지

restart를 사용하던지 편한것을 사용하면 되겠다.

방화벽은 나는 ec2 서버의 ubuntu를 사용하였기 때문에

인바운드 규칙에서 22번을 열어주면 방화벽 설정은 따로 해줄 필요가 없었다.

그리고 이제

접속할 쪽에서 openssh-client를 받아준다.

윈도우는 여기서 다루지 않겠다. 다른곳 가면 잘 나온다.

나는 WSL2를 사용해서

sudo apt-get install openssh-client

를 해줘서 패키지를 다운받았다. 그리고 나서 접속하는 명령어는

기존 명령어부터 먼저 보자면,

ssh -i "private key.pem" username@ip_address

방식이었고 pem파일이 없으면 실행이 안됐다.

설정법

일단 설치는 다 진행했고 다시 ec2서버로 가서

vi /etc/ssh/sshd_config 파일을 열어준다.

여기서 openssh-server에 대한 설정을 해줄 수가 있는데

전부 주석으로 처음에 막혀있다.

스크린샷 2021-11-11 오후 8 58 52

이렇게 주석되어 있는 소스들을 풀어주었다.

포트하고 어느 주소만 허용할 것인지

나는 언제 어디서나 사용할 수 있게끔 ip를 다 열어주었다. 사내에서는 이렇게 하면 안될 것이다.

막혔던 부분

아니 나는 당연히 여기서 무조건 되겠지 하고 걍했다.

남들 하는것처럼 비밀번호 나오겠지 뭐

또는 아? 인증화면에서 yes 해서 known_hosts에 등록되겠지 했다.

ㅋㅋㅋㅋㅋㅋㅋㅋ 미친짓이었다.🔥

해결책

🤔 왜 안됐을까?

아니 비밀번호를 설정만 해주면 뭐하나

일단 비밀번호 설정을 모르는 사람들을 위해 명령어 한줄 치고 가겠다.

나는 default username인 ubuntu를 썼다. 그리고 ubuntu 계정으로 들어와있다.

스크린샷 2021-11-11 오후 9 08 45

그렇기때문에

뭐 모르는상태에서 변경해야 될 수도 있을때는 sudo를 붙이면 되고,

물론 root가 아닌 얘기다!!!

sudo passwd username 으로 비밀번호를 설정해주면 된다.

여기까진 당연한것이고...

아까 설정했던 부분인 vi /etc/ssh/sshd_config로 다시 가보자

아래로 내려보면서 찾아보면

스크린샷 2021-11-11 오후 9 13 13

PasswordAuthentication 설정이 있다.

비밀번호 인증 ? yes 를 해야 당연히 적용이되지 바보같은것..

그래서 이제는 이 변경 설정을 적용해줘야 하기 때문에

위에서 봤던 명령어로 다시 껐다가 켜주면 정상적으로 로그인이 된다!!!

스크린샷 2021-11-11 오후 9 14 57

이렇게 ssh 접속으로 비밀번호까지 쳐주게 되면?

스크린샷 2021-11-11 오후 9 17 14

잘 나오게 된다. 아주 편리하고 좋은 것 같다.

이제 여기에 여러가지 설정들을 할것이다❗️❗️❗️❗️❗️ 🔥🔥🔥🔥

728x90

'클라우드' 카테고리의 다른 글

Docker compose  (0) 2022.08.07
[Docker] 도커란  (0) 2022.08.05
728x90

모든 코드는 깃허브에 있음을 알린다.

ALU

열심히 한 끝에 결국 2장의 마지막 끝판왕 ALU까지 오게 되었다.

아 이 ALU를 하려고 1장부터 몇번을 다시 봤는지 모르겠고,

기초를 차곡차곡 쌓는게 중요하다고 다시한번 느낀 그런 날이다.

이전 내용은 2장 불연산 앞부분 회고를 참고하면 되겠다.

일단 ALU는 산술논리연산장치 로서

덧셈, 뺄셈 같은 두 숫자의 산술연산과 배타적 논리합, 논리곱,

논리합 같은 논리연산을 계산하는 디지털 회로이다.

ALU는 CPU의 내부 구성 요소중 하나이며, 실제적인 연산을 담당하는 하드웨어 모듈이다.

컴퓨터시스템의 다른 부품들은 사실 ALU가 연산하기 위해 처리될

데이터를 가져오거나,

그 결과를 저장하기 위한 용도라고 해도 틀린 말이 아니다.

ALU는 구성이 산술, 그리고 논리로 되어있다.

산술

ALU는 덧셈만 있지 나눗셈, 곱셉은 존재하지 않는다.

곱셈은 그냥 * 5 해줄것을 5번 더해주는것으로 생각하고

나눗셈은 / 5 해주는 것을 5번 빼주는식으로 해주면 된다.

그래서 ALU는 덧셈으로 산술연산을 모두 처리할 수 있다.

논리

이 논리 연산으로 같은 값인지? 다른값인지 누가더 큰지 이런것을 비교하여 값을 받을 수가 있다.

회로도

조건식은

  • zx
    • if zx then x = 0
  • nx
    • if nx then x = !x
  • zy
    • if zy then y = 0
  • ny
    • if ny then y = !y
  • f
    • if f then out = x + y else out = x & y
  • no
    • if no then out = !out

이 셀렉터들의 조건이 있다.

그리고 아래는 회로도를 직접 그려본 것이다.

IMG_2742

처음에 위의 저 조건들은 당연히 이해를 했다.

그래서 아 이것도 쉽구나 하고 쭉 구현해보려고 했는데 문제가 있는 부분은

현재 그림으로 no 셀렉터 값을 가진 16비트 멀티플렉서 를 지나는 지점부터 이해가 잘 되지 않았다.

왜냐면 생각이 안들기 때문이었다. ㅋㅋㅋㅋㅋㅋ

그러니까 기초가 부족했던 탓이라고 생각했다. 😅

이제 출력값이 세개였는데

Output : 16비트 출력
zr : out = 0 일때 True 출력
ng : out < 0 일 때만 출력

이 세개가 왜 나와야하는지 그리고 저게 뭘 의미하는지 사실 잘 몰랐었다.

근데 2장을 앞부분으로 되돌아가면

16비트는 정상 출력을해주고

out이 0보다 작다는 소리는 맨 앞부분이 부호비트 라는 것을 생각해보면

아! 맨 앞 비트를 뽑아주면 되는구나!

까지 생각했다.

그래서 저 두개 부분은 금방 해결을 했다.

마지막 해결 부분

마지막 out = 0 일때 True 출력

이 부분은 사실 8개씩 나누어 두개의 Or8Way 를 사용하였다.

비트를 압축해야 하는데 자른 8개의 비트중에 하나라도 1이라면 그냥 1이 되게 되어있다.

그래서 no 라는 셀렉터가 값을 반전시키기 때문에 전부 0이 나올때만 1로 출력되게 해주어야 했다.

그래서 Or8Way로 비트를 2개로 압축한다. 이 결과를 다시 Or조건으로 넣으면

16비트 전부 0이 나오면 이제 그 0을 반대값으로 환산해서 모두가 0일때

참으로 나와주게끔 구현을 한 것이다.

아래는 nand2tetris의 코드이다.

CHIP ALU {
    IN  
        x[16], y[16],  // 16-bit 입력        
        zx,
        nx,
        zy,
        ny,
        f,
        no;

    OUT 
        out[16],
        zr,
        ng;

    PARTS:

    // zx, zy
    Mux16(a=x, b=false, sel=zx, out=o1);
    Mux16(a=y, b=false, sel=zy, out=o2);

    //nx
    Not16(in=o1, out=noto1);
    Mux16(a=o1, b=noto1, sel=nx, out=o3);

    //ny
    Not16(in=o2, out=noto2);
    Mux16(a=o2, b=noto2, sel=ny, out=o4);

    // f연산
    And16(a=o3, b=o4, out=andxy);
    Add16(a=o3, b=o4, out=addxy);
    //add or and 정의해놓고 멀티플렉서로 선택함
    Mux16(a=andxy, b=addxy, sel=f, out=result);

    //no연산
    Not16(in=result, out=notresult);

    //위와 같이 not 구현해놓고 not연산에 해당하는 값을 멀티플렉서로 선택함
    Mux16(a=result, b=notresult, sel=no, out=out, out[0..7]=front, out[8..15]=back, out[15]=ng);

    Or8Way(in=front, out=frontout);
    Or8Way(in=back, out=backout);
    Or(a=frontout, b=backout, out=orout);
    Not(in=orout, out=zr);

}

결론

드디어 끝을 보니까 너무 후련했다.

위의 코드를 HardwareSimulator를 실행해서 테스트 케이스를 통과할 때의 그 기분은 느낀 사람만 알 것이다.

확실히 손으로 그려보고 딱딱 만들어 가는 과정이 나는 더 잘 맞는것 같다.

근데 이 고통을 겪어서 그런가 감히 다음장에 나오는 플립플롭, 레지스터에 도전하고 싶다.

728x90

'CS' 카테고리의 다른 글

Cache  (0) 2022.08.09
불 연산 회고  (0) 2022.08.09
불 논리 정리 2  (0) 2022.08.09
불 논리 회고  (0) 2022.08.07

+ Recent posts