728x90

시작하기

내가 클라우드 요금은 견딜 수 없어서 상당히 겁이 난 상태였다.

 

하지만 뭔가 내가 만들어서 써보고 싶은게 최근에 생기게 됐다.

 

그게 바로 북마크인데, 크롬에 의존해서 북마크를 하는게 아니라, 내가 직접 만들어서 거기에 글 포스팅을 스크랩하거나,

 

모르는 영단어나 줄여쓰는 영어등등.. 개발 관련 단어들도 뭔가 한 곳에 두고 싶었다.

 

그런데 웬걸, 친구가 라즈베리파이가 한 개 남는다고 이거 써보겠냐고 물어왔다. ㅋㅋㅋㅋㅋ 나는 좋지~

아두이노는 한번 써봤는데 이건 처음이라 그래도 OS설치는 이미 되어있다고 해서 받아서 써보는데 되게 귀엽고 성능은 좋은것 같다? (아직 첫날이라 그런걸까.... 🤔)

 

아무튼 각설하고 이 서버를 통해서 ssl 적용도 해서 도메인 주소 붙이고 나만의 북마크를 만들어보고 싶어졌다!

 

이 라즈베리를 사용하는 덕에 뭔가 운영체제나 메모리, 네트워크 등등의 지식들을 같이 얻어갈 수 있는 기회라 좋을 것 같다!

 

이제 막 여러 것들을 설치하면서 하나씩 보는데 상당히 재밌다.

 

ㅋㅋㅋㅋ 월급받으면 이 라즈베리파이에 쿨러도 달아줄까 생각중이다. (종일 써야되니 무조건 달아줘야 하는게 아닐까 싶긴 하다)

 

이게 진짜 웃긴게 sd카드는 용량을 다시 한번 확인해봐도 16기가였다.

근데 전체 디스크 용량을 보면 5.6기가 남짓이다.

 

좀 구글링을 해봤는데, 라즈베리파이에서 제공되는 OS 사용시 메모리가 4기가 남짓만 사용가능하게 설정된다고 한다.

 

그래서 용량을 재설정하는 것을 검색해보고 적용하니까 아래같이 바뀌게 됐다!

 

크... 87%에서 33%로 감소하는 수준의 용량차이다. ㅋㅋㅋㅋㅋ

뭔가 지금 해결되지 않은건 와이파이 설정인데..

 

일단 기존에 친동생 방에 이더넷 케이블이 남는 여분이 하나 있어서 당장 쓰지 않는 케이블이라 이더넷으로 연결해서 사용중이다.

 

공유기로 포트 물린게 아니라 따로 회선이 등록되어 있는경우라서 공인 ip로 바로 접속해버렸다.

 

이거 철저하게 관리하고 공유기로 돌릴 수 있는 방법 찾으면 그렇게 바꿔야겠다.

 

뭐 일단 ssh접속 포트도 바꿨고, public key접속으로만 접속하게끔 허용해놓은 상태라서 일단 당분간은 안심하고 쓸수도 있겠다.

 

뭐 mysql은 일단 설치 잘했고...

 

가장 최신 LTS인 Java17을 나중에 받아야겠다. 뭔가 homebrew에서 하는 설치랑 조금 다르게 wget으로 수행해주는 것 같다.

 

아니면 지금 공부중인 코틀린을 써볼까?! 하는 재밌는 생각이 계속 들면서 기분이 좋아진다.

 

소프트웨어 장인이라는 책을 출퇴근 시간에 읽으면서 마음가짐도 되게 많이 바뀌게 되는것 같다.

 

인프콘도 다녀오면서 오프라인 컨퍼런스를 체험하고 다른 개발자분들의 의지에 덩달아 자극을 받아 열심히 또 달려보려고 한다!

스터디도 JVM 관련 스터디와 이펙티브 자바 회독을 하려고 이 스터디도 들어가게 되서 총 2개 스터디를 들어가게 됐다.

 

이것도 같이 병행하면서 열심히 해야겠다. (북마크 만드려면 JavaScript도 공부해야되는데...?)ㅋㅋㅋㅋ

 

아무튼 요근래 못했던 삽질 ATDD과정 끝나면서 이런 값진 행동들을 하면서 많이 에러 맞아볼 수 있는 또 하나의 기회가 오지 않았나 싶다.

마무리

공부할게 정말 많고 시간이 없어서 내 자신을 하나의 프로세스로 두고 여러 스레드를 할당해서 병렬로 지식습득을 하고 싶다 😇

 

요즘에 계속 TDD를 하려는 습관을 가지다 보니까 전에 단위 테스트를 작성했던 나보다 훨씬 예외 케이스나 엣지 케이스들을 더 잘 찾아내는것 같다.

 

확실히 반복적으로 짧게 테스트를 가져가면서 피드백 받으니 이렇게 되는건가 싶다.

 

728x90

'Diary' 카테고리의 다른 글

2022년 회고  (8) 2022.12.30
NHN Forward  (1) 2022.11.24
ATDD, 클린 코드 with Spring 5기 수료 회고  (0) 2022.08.14
블로그를 옮기고 최신 근황  (0) 2022.08.13
728x90

이 포스팅은 반효경 운영체제 강의 를 듣고 정리하는 글이다.

fork, exec실습 코드는 깃허브에 있다.

프로세스 생성

부모 프로세스는 1개만 존재하고, 자식 프로세스들을 생성한다.
copy on write (cow) 기법

주소 공간
자식은 부모의 공간을 복사한다.
자식은 그 공간에 새로운 프로그램을 올린다.

유닉스에서는 fork 시스템 콜 을 통해 부모 프로세스와 똑같이 복사한다.

수행

  • 부모 자식이 공존하며 수행되는 모델
  • 자식이 종료될 때까지 기다렸다가 수행되는 모델

프로세스 종료
프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알린다. (exit)

  • 자식이 부모에게 output data를 보낸다.
  • 프로세스의 각종 자원들이 운영체제에게 반납됨

부모 프로세스가 자식의 수행을 종료시킨다. (abort)

  • 자식 프로세스가 할당 자원의 한계치를 넘어설때
  • 자식에게 할당된 태스크가 더 이상 필요하지 않음
  • 부모가 종료하는 경우
    • 운영체제는 부모 프로세스가 종료하는 경우 자식이 더 이상 수행되도록 두지않음
    • 단계적인 종료

자식을 전부 죽이고서 부모가 죽게되는 구조

wait()
프로세스가 wait() 시스템 콜을 호출하게 되면,

  • 커널은 child가 종료될 때까지 프로세스 A를 sleep 시킴 (block 상태)
  • 자식 프로세스가 종료되면 커널은 프로세스 A를 깨운다 (ready 상태)

exit()
프로세스의 종료
exit()을 만나는 순간 바로 종료가 되게 된다.
자바로 하게되면 System.exit(0);

자발적 종료
마지막 작업 수행 후 exit() 시스템 콜을 통해 수행
프로그램에 명시적으로 넣어주지 않아도 main 함수가 return되는 위치에 컴파일러가 삽입해준다.

비 자발적 종료

  1. 부모 프로세스가 자식 프로세스를 강제 종료시킴
    • 자식 프로세스가 한계치를 넘어서는 자원 요청
    • 자식에게 할당된 작업이 더 이상 필요하지 않음
  2. 키보드로 kill, break를 수행한 경우
  3. 부모가 종료하는 경우
  4. 부모 프로세스가 종료하기 전에 자식들이 먼저 종료

프로세스간 협력

독립적 프로세스

프로세스는 각자의 주소 공간(코드, 스택, 데이터)을 가지고 수행되므로,
원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못한다.

협력 프로세스

프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스에 영향을 미칠 수 있다.

프로세스 협력 메커니즘 (IPC: Inter Process Communicaiton)

  1. 메세지를 전달하는 방법
    message passing : 커널을 통하여 메세지를 전달
    프로세스에게 직접 전달하는 방법, mailbox를 통한 전달방법
  2. 주소 공간을 공유하는 방법
    shared memory : 서로 다른 프로세스 간에 일부 주소 공간을 공유하게 하는 shared memory 메커니즘이 존재 (이 부분도 커널에게 공유하겠다는 시스템 콜을 이용해야한다)

program은 CPU를 사용하는 단계 (CPU burst) -> I/O를 수행하는 단계 (IO burst)를 계속 반복하며 실행된다.
(물론 CPU만 사용하는 프로그램도 존재할 수 있음)

여러종류의 job(=process)가 섞여있어 CPU스케줄링이 필요하다.
Interactive job에게 적절한 응답 제공하기 위함
CPU와 입출력 장치등의 시스템 자원들을 골고루 효율적으로 사용하기 위함

CPU 스케줄러
ready 상태의 프로세스 중 이번에 CPU를 줄 프로세스를 고름

Dispatcher
CPU의 제어권을 CPU 스케줄러에 의해 선택된 프로세스에게 넘김
이 과정을 context switching(문맥 교환)이라 한다.

728x90

'CS > 운영체제' 카테고리의 다른 글

운영체제 2강 - 2  (0) 2022.08.18
운영체제 2강 - 1  (2) 2022.08.11
운영체제 1강  (0) 2022.08.11
프로세스 상태  (0) 2022.08.10
728x90

동기식, 비동기식 입출력

프로세스가 입출력이 진행되는 동안에 CPU 점유는 중요하지 않음

입출력 명령을 계속 대기하면 동기,
해놓고서 다른 작업을 수행하면 비동기

구현 1

I/O가 끝날때 까지 계속 대기해 CPU를 낭비시킴
매시점 하나의 입출력만 일어남

구현 2

입출력이 완료될 때까지 해당 프로그램에서 CPU를 빼앗음
다른 프로그램에서 CPU를 가지고 연산 수행
입출력 작업이 완료되면 해당 프로세스에 CPU할당

스레드

프로세스 내부의 CPU 수행단위가 여러개 있는것이 스레드

프로세스마다 code, data, stack 이 주어짐

CPU수행과 관련된정보는 스레드가 별도로 가지고있고,
나머지는 공유한다.

스레드의 구성

program counter
register set
stack

스레드의 장점

  1. 다중스레드 구성된 작업구조에서는 하나의 스레드가 blocked 상태인 경우에도
    동일한 작업 내의 다른 스레드가 실행되어 빠른 처리가 가능하다.
    1, 2, 3 을 보여줄때
    먼저 완료된거 바로 보여주게 되면 사용자에게 좀더 원활한 서비스를 제공가능

  2. 자원 공유 - 같은 일을 하는 프로그램을 멀티 프로세스를 구성하기 보다는 cpu 수행단위만 여러개로 주게되면 자원을 효율적으로 사용이 가능하다.

  3. 같은 작업을 다중 스레드를 사용하여 동시처리가 좋아지고 성능 최적화를 할 수 있다.
    (병렬 처리)

  4. 여러개의 프로세스에서 각각의 스레드가 서로 다른 CPU에서 병렬적으로 작업 수행이 가능하다.

커널 스레드
커널의 지원을 받는 스레드
운영체제 커널이 여러 스레드로 구성되어 있는것을 알고있음

유저 스레드
라이브러리를 통해 지원, 프로세스안에 여러 스레드가 있다는것을 운영체제는 모름
구현사항에 제약은 있을 수 있음

프로세스

프로세스는 파일 형태로 존재하는(ex - xxx.exe 등) 프로그램이 실행되어 메모리에 올라가 있는 상태를 뜻한다.

잡(Job)이라는 용어와도 혼용해서 부른다.

프로세스를 이해하려면 프로세스 문맥(Context)도 알아야 한다.

프로세스 문맥(Context)

문맥은 해당 프로세스의 주소공간, 레지스터의 값, 시스템 콜 등등을 통해

커널에서 수행한 작업의 상태, 커널이 관리하고 있는 정보들을 담고있다.

프로그램이 프로세스가 되면, 운영체제는 프로세스를 관리하기 위한 자료구조를 유지한다.

이 두개가 바로 PCB, 커널스택이다.

프로세스의 상태에는 실행, 준비, 봉쇄가 있다.

  • 실행
    • 프로세스가 CPU를 보유, 기계어 명령을 실행하고 있는 상태
    • CPU는 하나뿐이므로, 여러 프로세스가 실행된다 하더라도 실행 상태에 있는 프로세스는 매 시점에 하나뿐
  • 준비
    • CPU를 할당 받지 못한 상태
  • 봉쇄
    • CPU를 할당받더라도 바로 명령을 실행할 수 없는 프로세스의 상태
    • 프로세스가 요청한 입출력 작업이 진행중인 경우 이에 해당함

프로세스의 상태를 구분하는 이유는 컴퓨터의 자원을 효율적으로 관리하기 위함

크게는 3가지이고

시작과 완료 상태가 존재한다.

프로세스가 시작되어 프로세스를 위한 자료구조는 생성됐는데 메모리를 획득하지 못한 경우를 시작상태,

프로세스와 관련된 자료구조를 완전히 정리하지 못한상태가 완료상태이다.

운영체제에게 입출력을 요구하건 다른 작업을 요구하면 모두 큐에 줄을 서게되고,

자기 차례가 되면 그때서야 데이터들을 받게되고, 그 때 인터럽트가 발생해서 입출력 완료 사실을 알림

프로세스 제어 블록 (Process Control Block; PCB)

PCB는 운영체제가 시스템 내의 프로세스들을 관리하기 위하여 프로세스마다 유지하는 정보들을 저장하는

커널 내의 자료구조이다. 구성 요소는 아래와 같다.

  • 프로세스의 상태
  • 프로그램 카운터 값
  • CPU 레지스터 값
  • CPU 스케줄링 정보 (메모리 할당을 위해 필요한 정보)
  • 메모리 관리 정보 (메모리 할당을 위해 필요한 정보)
  • 자원 사용 정보
  • 입출력 상태 정보

문맥교환 (Context Switching)

하나의 프로세스로부터 다른 프로세스로 CPU 제어권이 이동하는 것을 컨텍스트 스위칭이라 한다.

교환이 될 때 원래 CPU를 가지고 있던 프로세스는 프로세스의 문맥들을 PCB블록에 저장하고,

제어권을 받은 CPU는 자기 자신의 저장된 문맥을 PCB로부터 읽어온다. (이 과정이 성능을 느리게 할듯?)

인터럽트에서 설명을 정리했을 수 있는데,

프로세스 중간에 입출력을 해야된다거나 등등으로 운영체제를 사용해야 하는 경우에 시스템 콜을 하게 될텐데

이 때의 프로세스는 컨텍스트 스위칭이 아니라 그냥 해당 프로세스가 실행중인 것이다.

그래서 이러한 과정에선 컨텍스트 스위칭이라고 하지 않는다.

CPU를 점유하는 프로세스가 다른 사용자의 프로세스로 변경되어야만이 컨텍스트 스위칭이라 할 수 있다.

CPU 할당 시간을 아주 작게 설정하면 너무 많이 컨텍스트 스위칭이 일어나서 오버헤드가 커지며

성능이 저하될 것이다.

반대로 너무 시간을 길게 잡게되면 시분할 시스템의 의미가 퇴색될 수 있다.

728x90

'CS > 운영체제' 카테고리의 다른 글

운영체제 3강  (0) 2022.08.22
운영체제 2강 - 1  (2) 2022.08.11
운영체제 1강  (0) 2022.08.11
프로세스 상태  (0) 2022.08.10
728x90

이전에 TDD, Clean Code with Java 12기를 이수하면서,

 

테스트 코드에대한 중요성 그리고 단위 테스트는 어떻게 해야겠다! 라고 깨달음을 얻었었다.

 

그렇게 하면서 업무에도 테스트코드를 적용하려고 하는데

 

흔히 Spring framework에서 사용하는 Controller, Service Layer 들의 테스트들을 내가 작성할 때에는 전부 Mock을 이용해서

테스트를 진행을 해주었다.

 

이렇게 해주는게 맞을까? 🤔 라는 의문을 계속 가지면서 그리고 최근에 뭔가 조금 해이해진 경향을 바로 잡고자

 

이 강의를 신청하게 되었다.

 

각 주차마다의 미션들을 좀 가져와봤다. 궁금하면 한번 놀러와주세요!

1주차 - 인수 테스트

https://github.com/lsj8367/atdd-subway-map

 

GitHub - lsj8367/atdd-subway-map: ATDD 과정 저장소 - 지하철 노선도 관리 미션

ATDD 과정 저장소 - 지하철 노선도 관리 미션. Contribute to lsj8367/atdd-subway-map development by creating an account on GitHub.

github.com

2주차 - 인수 테스트와 TDD

https://github.com/lsj8367/atdd-subway-path

 

GitHub - lsj8367/atdd-subway-path: ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션

ATDD 과정 저장소 - 지하철 노선도 경로 찾기 미션. Contribute to lsj8367/atdd-subway-path development by creating an account on GitHub.

github.com

3주차 - 인수 테스트와 인증

https://github.com/lsj8367/atdd-subway-favorite

 

GitHub - lsj8367/atdd-subway-favorite: ATDD 과정 저장소 - 인증 기반 인수 테스트 미션

ATDD 과정 저장소 - 인증 기반 인수 테스트 미션. Contribute to lsj8367/atdd-subway-favorite development by creating an account on GitHub.

github.com

4주차 - 인수 테스트와 리팩토링

https://github.com/lsj8367/atdd-subway-fare

 

GitHub - lsj8367/atdd-subway-fare: ATDD 과정 저장소 - 테스트 기반 문서화 미션

ATDD 과정 저장소 - 테스트 기반 문서화 미션. Contribute to lsj8367/atdd-subway-fare development by creating an account on GitHub.

github.com

이렇게 4주까지 피드백 받으면서 인수 테스트를 작성했더니 시나리오 흐름대로 뭔가 이해할 수 있게 테스트를 구성하는게 가능해진것 같다.

 

또 한번 사석에서 리뷰를 받아보니까 어떤 부분을 내가 놓치고 있었고, 어떤 부분에서 조금 더 나은 선택을 할 수 있는지 많은 고민을

할 수 있어서 굉장히 좋았다.

 

RestAssured를 사용해서 인수테스트를 진행했는데, MockMvc와의 다른점과 좋았던 점 등등을 다른 포스팅에서 다시 쓰도록 하겠다.

(여기는 회고글이기 때문)

 

한 프로젝트를 돌아가게 작성만 해놓고 다른 프로젝트를 또 시작하는 SI 형식의 개발이 아니기 때문에,

 

내가 짠 코드에 대해서 유지보수를 잘하게끔 도와주고, 더 나아가서는 코드를 다른사람이 볼 때에도 이해하기 쉽게끔 테스트를 구성하는게 습관이 되어가고있다.

 

이와 동시에 비즈니스 코드를 작성하는것보다 무조건 테스트 클래스부터 만드는 내 자신을 볼 때 좀 뿌듯한 감정도 있다. ㅋㅋㅋㅋ

 

다만 이 과정중에 아쉬웠던 것은, 조금 더 여유있을 때 해볼걸 하는 생각도 있지만 끝나고 보니 열심히 하는 다른 분들이 있어서

 

같이 자극받고 하지 않았나 싶다!!

이제는 다시 업무를 위한 공부를 하면서, 틈틈이 하려고했던 운영체제, 네트워크, 알고리즘을 쭉 해야겠다.

 

아무튼 좋았고 되게 값진 경험이었다!

728x90

'Diary' 카테고리의 다른 글

NHN Forward  (1) 2022.11.24
라즈베리파이 사용  (0) 2022.08.31
블로그를 옮기고 최신 근황  (0) 2022.08.13
업무 리팩토링에 대한 회고  (0) 2022.08.10
728x90

블로그를 기존 벨로그에서 티스토리로 옮기게 되었다.

 

그래서 거기서 포스팅 했던 글들을 전부 지금 이 티스토리로 옮기고

 

드디어!! 진짜 날짜에 맞는 첫글을 써본다.

 

이직

우선, 전 직장에서 6개월을 하고 그만 둬버렸다. 뭔가 시도해보고 도입해보려고 했던건 많이 도입을 해보았던 것 같다.

 

내 입장에서는 그래도 같은 업무를 보는 개발자분들이 좀 더 많았으면 좋겠다고 생각했고,

 

여러 의견을 들어보고 싶었던게 가장 컸던 것 같다. 그래서 이직을 하게 됐다!!

 

물론 근속기간이 짧고 그렇게 이르게 이직을 했다는 것 자체가 문제라고 본다면 문제일 수 있다고 생각하지만,

 

뚜렷한 기준을 가지고 충분히 설명할 수 있을거라고 생각했었다.

 

B2C 서비스를 하던 기존 회사였지만, 그래도 아직 시장이 되게 작았었고,

 

그것으로 인해 여러 트래픽 경험을 못해봤던게 조금 아쉬움이 많이 남았는데..

 

B2B 핀테크 서비스로 옮기면서 전 회사가 작았던 탓인지 지금 이곳이 트래픽이 훨씬 많다!!!

 

무엇보다 이직한지 1달이 조금 넘은 지금은 CTO님이 계셔서 탄탄하게 흘러가고 있다는 것이 좋게 느껴진다.

 

다들 고민을 위해서 뭐든지 뛰어들고 공유해보고 얘기하는 것을 좋아하시는 것 같다고 내 주관적으로 생각한다.

 

아키텍처 리뷰

지금 당장 내가 담당하고 있는 일은 공통 메세지 서버를 개발을 진행중이다.

 

서비스가 점점 커지며 MSA 아키텍처로 전환하면서 지금의 회사는 Kafka를 도입했다. (이 포스팅에 대해선 추후에 다시 다루도록 하겠다. 😁)

 

이렇게 분리하면서 나온게 지금 내가 개발하고 있는 공통 메세지 서버이다.

 

간단하게 설명하자면 여러 메세지들을 푸시, 메일, 슬랙 등등으로 공지나 알람들을 보내주게 되는 서버이다.

 

처음 써보는 Kafka지만 그래도 다들 많이들 도와주셔서 잘 구축한거라고 생각한다.

 

작성 시점 기준으로 어제 아키텍처 리뷰를 받았는데, 면접 때 질문 받은것보다 이상으로 더 많이 깊게 물어봐주시고 알려주시고 한 덕분에

 

얻어가는게 많은것 같다. ㅋㅋㅋㅋ (아직 한참 모자라다고 많이 느낀다)

 

코드리뷰도 적극적으로 한 PR에 코드 수정이 아니더라도 생각할 수 있게끔 주시는 코멘트도 많아서

 

답변까지 포함해서 첫 PR기준으로 4~50개가 달렸었다. 각기 다른 시각으로 봐주는게 내 입장에서는 정말 좋다고 느꼈다.

 

개인적으로는 이직 후에 1달이 지난 지금이지만, 각 서비스에 대한 도메인 지식은 지금이 훨씬 습득이 빠른것 같다.

 

VOC도 처리하면서 온보딩 받아가며 하다보니 잘 잡혀가는 것 같다.

 

그래도 계속 내가 성장하는데 발목을 잡는건 CS였다. 뭔가 잘 개발하는것 같다가도 궁금증이 생기는 건 무조건 CS였다.

 

그래서 내가 생각했던 방법은...

 

모각코를 해서 다양한 사람들의 의견도 들어보고 내 공부도 틀어지는 것 같을때 질문해서 방향성 잡아 좀 깊게 하고 싶었다.

 

그래서 최근에 내가 거주하는 지역에서 오프라인 모각코를 모집했다.

 

내가 뭔가 주도해서 소통하고 싶고 다른 분들의 생각도 들으면서 공유하고 싶었던게 크다.

 

스터디 구인 글

 

부천 일요일 모각코 모집합니다. - 인프런 | 스터디

스터디 주제 : 모각코 예상 스터디 일정(횟수) : 2개월간 주 1회(일요일) 안녕하세요 부천 일요일 모각코를 모집합니다! 시간은 10:00 ~ 13:00 이며, 현재 인원은 6명으로 구성이 되어있고, 추가적으로

www.inflearn.com

이렇게 모집하면서 나를 포함한 8명이 내일이면 처음 모각코를 시작한다.

 

지속적으로 잘 운영이 된다면 인원도 늘려서 소규모로 자기가 배웠던것이나 최근 이슈를 해결했던 것을 발표하는 시간도 가져보고 싶은

 

생각이 있다. (커지면 해당 노션도 이렇게 운영되고 있다 라고 보여주려고 공유할 생각이다!)

 

계속 생각하는 것이지만 커머스, 금융 도메인에서 뭔가 돈이 관련된 서비스들을 주로 다루어보고 싶었다.

 

누군가가 보면 피곤하다고 생각이 들 수 있는데, 예민한 정보이고 깐깐하게 검증을 수행해주어야 하기 때문에

 

더 탄탄하게 많은 경우의 수를 가지면서 다룰 수 있을거라고 생각해서 이쪽으로 관심을 가지게 되었던 것 같다.

 

이제는 지금 회사에서 꾸준히 잘 성장하다보면 언젠가는 내가 원하는 회사에 갈 수 있지 않을까 생각한다.

 

다음 회고에서는 지금보다 더 성장하는 내가 되었으면 하고 계속 반복적인 연습이 답인것 같다. 🔥🔥🔥🔥

728x90

'Diary' 카테고리의 다른 글

라즈베리파이 사용  (0) 2022.08.31
ATDD, 클린 코드 with Spring 5기 수료 회고  (0) 2022.08.14
업무 리팩토링에 대한 회고  (0) 2022.08.10
업무 회고  (0) 2022.08.07
728x90

반효경 운영체제 강의 정리

컴퓨터 시스템 2

사용자 프로그램이 운영체제를 통해서 무언가를 해야할 때에는 시스템 콜을 이용한다!

프로그램 카운터 (PC, Program Counter)

프로그램 카운터는 CPU안에 포함되어 있는데,
이는, 메모리에서 다음 실행되어야할 작업의 주소가 들어있다.
CPU는 이 주소를 바탕으로 다음 작업을 찾아가 작업을 수행하게 된다.

 

인터럽트가 발생한다면?

 

인터럽트가 발생한 경우에는, 프로그램 카운터가 가리키고 있는 주소로 바로 이동하지 않고,
CPU제어권이 OS로 가게 되면서 해당 인터럽트를 우선으로 처리해준다.

동기식, 비동기식 입출력

비교적 최근에 내가 29cm의 지원을 했을때를 떠올려본다.
그때 당시, 자바에서 csv파일을 읽어들여야 하는 작업이 있었다.
그 부분을 토대로 이해해보면
과제에서 csv를 읽어들이는 로직이 필요했는데, 자바에서 다른 작업을 OS에서 메모리를 할당받고, CPU를 할당받게 되면서 잘 진행하다가 파일을 읽어들이는 I/O 관련 작업을 실행하려고 할 때 디스크로 부터 가져와야 하니 커널모드의 운영체제가 필요했을 것이다.
근데 이 맥락에서 자바 프로세스는 CSV파일을 읽어들이는 I/O관련 로직은 사용자 프로그램에서는 진행할 수 없기에 시스템콜을 위한 인터럽트를 발생시킨다.
이렇게 발생되면 CPU는 인터럽트를 감지하여 주도권을 OS에게 넘기고,
OS는 하드디스크를 관리하는 디바이스 컨트롤러에게 위임하여 csv파일을 읽도록 위임하면서, 동기식이라면 디바이스 컨트롤러가 로컬 버퍼에 적재하는 작업을 모두 완료
할때까지 대기한다.
디바이스 컨트롤러가 이작업을 완료하는데까지 CPU는 대기상태에 들어가게 될 것이다.
그리고선 디바이스 컨트롤러가 완료했다는 인터럽트를 발생시키면
CPU는 다시 자바 프로세스에 할당이 된다음 다음 작업을 처리할 것이다.
다시 할당되는 과정을 질문을 해보았는데,
cpu 디스패처로 할당하는것 같다고 답변을 들었다. (이부분은 좀 더 뒤에서 다뤄봐야할듯?)

동기식 입출력

  1. I/O 입출력 작업이 완료되어야 다른 작업 진행 (완료되기 전까지 대기)

이 방법은 완료되기전 까지 대기한다면 CPU가 낭비된다.
매 시점에 하나의 입출력만 가능하게 된다.

  1. I/O 입출력 요청 후 다른 프로세스에게 CPU를 토스한다.

넘겨받은 CPU도 I/O를 요청하여 계속 다음 프로세스에게 CPU를 토스할 수 있다.

비동기식 입출력

비동기식은 I/O입출력을 요청한 후에
바로 CPU 제어권을 얻어서 다른 작업들을 쭉쭉 수행하다가
입출력 작업이 완료되면 인터럽트를 발생시켜줘서 완료 시점을 캐치하게 된다.

두가지 모두 인터럽트를 통하여 알려준다는 사실은 동일하다!!!

Memory Mapped I/O

일반적인 입출력은 메모리에 접근하는 명령과 I/O 장치를 접근해야 하는 명령이 구분되어 있다.
Memory Mapped I/O는 I/O 장치에 대해 메모리 주소를 할당해놓은것이다.
주소로 호출을 하게 되면 그것이 I/O를 하게 되는 것이다.

저장장치 계층 구조

레지스터 -> 캐시 메모리(SRAM) -> 메인 메모리(DRAM) ->
(CPU가 여기서부터 직접 접근 불가) 하드 디스크 -> optical disk -> magnetic tape
이 순서의 계층 구조로 되어있으며, 오른쪽으로 가면 갈수록 비용이 싸면서, 용량 증가하고 속도는 느려진다.

 

이미지 출처 - http://www.kocw.net/home/search/kemView.do?kemId=1046323

 

어떤 프로그램을 실행시키면 메모리의 프로세스로 올라가는데
중간단계를 거치는 것이 가상 메모리
A라는 프로그램의 주소공간 0번지부터 시작하는것이 생기게 된다. (각 프로그램마다)
stack, data, code를 가지고 있음

  • code
    • 인터럽트, 시스템콜 처리 코드
    • 자원 관리 위한 코드
    • 서비스 제공을 위한 코드
    • 쉽게말해 CPU와 소통할 커널부분의 코드들이 적재됨
  • data
    • pcb, cpu, memory, disk 와 소통 가능
  • stack
    • 프로세스 마다의 스택이 생성됨

종료시키면 가상 메모리가 사라짐
통째로 메모리에 올리면 메모리가 낭비되기 때문에
현재 실행되는것만 메모리에서 사용했다가 더이상 사용하지 않으면 제거
커널은 부팅하고나서 띄워지면 항상 상주
결국 프로그램은 커널모드, 유저모드를 반복하다가 끝나게 되는것

728x90

'CS > 운영체제' 카테고리의 다른 글

운영체제 3강  (0) 2022.08.22
운영체제 2강 - 2  (0) 2022.08.18
운영체제 1강  (0) 2022.08.11
프로세스 상태  (0) 2022.08.10
728x90

반효경 교수님의 운영체제 강의를 정리하는 포스팅
System Structure & Program Execution 1

📌 컴퓨터 시스템 구조

컴퓨터 시스템의 구조는 아래와 같다.

이미지 출처 - https://asfirstalways.tistory.com/115

컴퓨터 시스템에는 크게 중앙 처리장치인 CPU, 메모리, 그리고 외부 장치들인 디스크, 키보드 등등
으로 구분된다.
컴퓨터는 외부에서부터 데이터를 읽어와 연산을 한 후에 다시 출력해주는 방식으로 처리한다.

📌 CPU

CPUMemory에 올라간 프로그램들의 명령들을 하나하나 읽어들여 수행하는 역할을 담당한다.

I/O마저도 이 CPU가 관리하게 되면, 너무나도 많은 인터럽트가 발생하게 된다.

이런 경우에는 CPU가 효율적이지 못하다고 할 수 있다. (오버헤드가 너무 큼)
그래서 이것을 방지하게 나오는 것이 바로 DMA(Direct Memory Access) Controller를 사용한다.

접근 범위

CPU가 접근 가능한 곳은 메모리와 Local Buffer이다.

Interrupt Line

CPU는 자기가 처리하던 연산 중간에 인터럽트가 발생하게 되면, 하던일을 두고
인터럽트에 관련된 업무를 먼저 처리한다.

Mode Bit

운영체제에는 두가지의 모드가 존재하는데,

  1. 유저모드
  2. 커널모드

두가지 모드가 존재하는 이유는, I/O장치들을 보호하기 위해서이다.
모든걸 조작해서 악의적인 프로그램을 만들어서 I/O 장치에 접근할 수 없게하고, 운영체제를 통해서만
I/O를 수행할 수 있게 하는것.

  • 유저모드
    • 어플리케이션이 실행되는 영역
  • 커널모드
    • 프로그램들이 잘 수행되다가 인터럽트가 발생되어 운영체제가 호출되어 수행되는 영역

프로세스가 사용자 모드에서 작업을 수행하다 중요한 작업을 수행해야 할 경우에는 System Call(소프트웨어 인터럽트)을 통해 운영체제에게 서비스를 대신해 줄 것을 요청하게 된다.
그러면 CPU의 제어권은 다시 운영체제로 넘어가게 되고 인터럽트가 발생할 때에는 모드 비트가 자동적으로 0(커널모드)으로 세팅되어 필요한 작업을 수행하고 요청된 작업이 끝나게 되면 모드 비트는 다시 1(유저모드)로 만들어 사용자 프로그램에게 CPU를 넘겨주게 된다.

📌 DMA (Direct Memory Access) Controller

DMA 컨트롤러는 Local Buffer에 저장된 데이터들을 메모리로 복사하는 작업이 완료 되면,
그때만 CPU에게 인터럽트를 발생시킨다.

📌 Memory Controller

메모리 컨트롤러는 현재 위의 구조대로면, CPU와 DMA 컨트롤러가 서로 메모리에 접근이 가능하다.

그래서 만약 CPU, DMA가 동시에 접근하는 경우 데이터의 일관성이 깨질 수 있기 때문에

서로의 사용을 분배해주는게 바로 이 메모리 컨트롤러이다.

📌 Device Controller

이 컨트롤러는 해당 I/O를 관리하는 작은 CPU개념이다.
제어 정보를 위해 control, status register를 가진다.
Local Buffer(실제 데이터 저장)를 가진다.
I/O는 Device와 Local Buffer 사이에서 일어난다.
I/O가 끝난 경우에는 CPU에게 인터럽트로 알리게 된다. (DMA가 존재하면 DMA Controller)

Local Buffer

디바이스 컨트롤러가 데이터를 임시로 저장하기 위한 작업 공간.

Device Driver도 있는데 이는 CPU가 실행하는 각 디바이스들에 접근하기 위한 소프트웨어이다.

📌 Timer

무한 루프문을 돌게되는 어떤 프로그램만 CPU를 독점하는 상황이 생길 수 있다.
이럴 때를 대비해서 만든 것이 바로 타이머이다.
타이머는 특정 프로그램이 CPU를 독점하는 것을 막아주는 역할을 수행한다.
컴퓨터를 처음 시작하면 운영 체제가 CPU를 가지고 있다가 사용자 프로그램에게 CPU를 넘겨준다.
이 때, 그냥 넘겨 주지 않고 타이머 값을 설정하고 넘겨준다.
어떤 프로그램이 설정된 Timer의 값이 0이 되었을 때 타이머 인터럽트가 발생하여 다른 프로그램에게 CPU를 넘겨준다.

📌 Interrupt

CPU가 한개의 작업밖에 수행할 수 없는데,
하나의 작업을 수행중에 I/O가 발생하거나, 다른 우선 순위가 급한일이 생기게 되면 이 인터럽트가 발생된다.
키보드에서 'a'라는 키를 누르게 되면, 이 키의 코드값이 Local Buffer에 저장되고 인터럽트가 발생해서
처리하고 있던 작업을 인터럽트가 발생하기 직전까지의 정보를 저장(여기가 바로 PCB) 하는 인터럽트 처리 루틴을 수행한다.
인터럽트는 소프트웨어, 하드웨어 인터럽트 2개가 존재한다.

소프트웨어 인터럽트

돌다가 운영체제에게 대신 해달라고 요청할 경우 인터럽트를 발생시킬 수 있음
종류 - 예외 상황, System Call

하드웨어 인터럽트

하드웨어가 발생시키는 인터럽트로, CPU가 아닌 다른 하드웨어 장치가 cpu에 어떤 사실을 알려주거나 cpu 서비스를 요청해야 할 경우 발생시킨다.
ex - I/O완료 인터럽트 발생 (하드웨어 인터럽트)

인터럽트 벡터

인터럽트 처리 루틴 주소를 알고 있다. 종류마다 그 인터럽트가 발생하면 어디있는 함수를 실행하는지

인터럽트 처리 루틴

인터럽트 처리 루틴을 통해 해당하는 인터럽트 처리를 완료하고 나면 원래 수행하던 작업으로 돌아갈 위치를 알아야 하고,
인터럽트 처리 전에 수행 중이던 작업이 무엇이었는지 반드시 저장해야 한다.
그래서 운영 체제는 PCB라는 공간을 별도로 가지고 있다.

📌 System call

사용자 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출
모든 입출력 명령은 운영 체제만 사용할 수 있는 특권 명령으로만 가능하다. 그래서 사용자 프로그램은 이 시스템 콜을 활용한다.

728x90

'CS > 운영체제' 카테고리의 다른 글

운영체제 3강  (0) 2022.08.22
운영체제 2강 - 2  (0) 2022.08.18
운영체제 2강 - 1  (2) 2022.08.11
프로세스 상태  (0) 2022.08.10
728x90

자바 변성 (Variance)

자바의 가변성에는 크게 공변, 무공변, 반공변이 존재한다.
제네릭을 잘 사용하려면 이 가변성에 대한 이해가 필요하다.

변성을 제대로 이해하려면 "타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입인가?" 라는 질문에서 시작하는게 좋다.

배열은 공변, 제네릭은 무공변이 기본이라고 다들 알고 있을 것이다.

무공변 (Invariance) or 불공변

기본적으로 제네릭은 무공변이다.

무공변이라고 하니 헷갈리는것 같다. 사전적으로 번역해보면 불공변으로 나오게 된다.

타입 S가 T의 하위 타입일 때, Box[S]와 Box[T] 사이에 상속 관계가 없는 것

쉽게 말하면 너는너, 나는 나 인 느낌이다.
그래서 선언한 유형만 들어갈 수 있게 코드를 구성할 수 있다.

Object에는 Object만, String에는 String만 들어갈 수 있단 얘기이다.

void invariance() {
    // 제네릭은 기본적으로 무공변
    List<Object> objectList = new ArrayList<>();
    List<String> stringList = new ArrayList<>();

    objectList.add(1);
    objectList.add(1.0);

    stringList.add("aaaaa");
}

공변

공변(covariance)는 타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입 임을 나타내는 개념

@Test
void arrayTest() {
    Object[] arr = new Long[5]; //배열에서는 공변이고, Long은 Object의 하위타입이기에 할당이 가능하다.
    arr[0] = "arr"; //공변으로 인해 선언한 arr은 Object로 참조가 된상태라 String도 할당 가능.
    // 여기서 런타임에 ArrayStoreException 발생
}

자바에서 이 배열을 공변으로 열어두지 않았다면, 다형성의 이점을 살릴 수 없게 됐을 수 있다.

Arrays.swap()

Arrays의 메소드를 하나를 가져와봤는데,
만약 공변이 아니었다면, 이 배열 스왑 메소드는 객체별로 전부 구현해주어야 했을 것이다.
제네릭이 있기전엔 형변환에 대한 에러가 나더라도,
다형성의 장점으로 얻을 수 있는 이득이 많았을 것 같다.

리스트의 공변

void variance() {
    List<? extends Object> list = new ArrayList<Number>();
    list.add(1); //컴파일 에러
    list.add(1.0); //컴파일 에러
    list.get(0); // 정상 로직
}

이처럼 선언을 했을때 add는 선언된 제네릭으로 변수를 넣게 되어있는데,

무공변으로 만들었을 경우

공변인 경우

위와 같은 경우에는 capture of ? extends Object e Object의 하위타입은 맞지만,

어떤 타입인지는 모른다? 라는 뜻이라고 생각된다.

그래서 list.get(0)이 Object로 형변환은 가능하지만, 반대로 add()를 통해 null을 제외한 무언가를 추가해줄 수는 없다는 소리이다. 안에 들어가는 객체가 정확하게 뭔지 모르기 때문이다.

그래서 정확한 타입이 어떤건지는 모르기 때문에 개발자가 null을 제외하고는 아무것도 추가하지 못하게 막을 수 있다라고 봐도 될 것 같다.
그래서 자주쓰던 Collections 클래스의 UnmodifiableList를 찾아보게 되었다.

생성자에 이런식으로 공변을 이용해서 막아주고 있는것을 볼 수 있었다.
그러면서 List의 구현체이기 때문에 밖에서는 add에 어떤 값을 넣어줄 수는 있기에, 그대로

Override로 재정의 한 뒤에 Exception을 던져주게 만든것을 확인할 수 있었다.

반공변(Contravariance)

반공변 처음 봤을때 반만 된다 이런생각을 했었다.ㅋㅋㅋㅋㅋㅋㅋ

그게 아니라 공변의 반대

타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 상위 타입 임을 나타내는 개념입니다.

@Test
void contravariance() {
    List<? super Number> list = new ArrayList<>();
    list.add(1.0);

    final Number number = (Number) list.get(0);
    final Object object = list.get(0);
}

Number를 포함한 Number의 상위 타입들만 들어갈 수 있게 설정한 상태이다.

아까는 하위타입이 뭔지 알 수가 없다는 것이었는데,

이 코드는 Number 상위인건 알겠는데 상위 누구인지를 알 수 없는 상태이다.

super키워드 다음에 붙은 클래스까지의 형은 전부 넣을 수 있다는 소리와도 같다.

다시말하면, 최소 Number 타입은 보장이 된다는 소리와 같다.
그래서 list.get(0); 에서 최상 타입인 Object로 꺼내서

형에 맞는 캐스팅 or instanceof를 통해 값을 읽어오는게 가능하다.

마무리

이렇게 자바의 가변성에 대해 알아보았다.
얼추 정리되면서 감은 잡은것 같다.
PECS(Producer Extends Consumer Super)를 보면서,
일반적으로 소비(Consume)라는게 스타크래프트의 디파일러가 저글링을 컨슘해서 저글링을 잡아먹기때문에,

스타크래프트의 컨슘

어떤 컬렉션이 만들어지는 과정이 컨슘이라고 생각하고 값을 빼내는 과정(get)이 동작한다고 알고 있었다.
반대로 생산자(Producer)는 말그대로 생산이기에 값을 생성해주는(new) or 더해주는(add) 것이 생산자로 알고 있었다.
반대로 알고있던 것이다.

올바른 내용

컬렉션을 뒤져서 어떤 작업들을 처리 해주어야 한다면 그게 바로 컬렉션 값을 빼내(get) 뭔가를 만들기 때문에 생산자가 되어 extends를 사용해야 한다는 것이고,

컬렉션에 값을 추가해야되면 매개변수로 주어진 값이 소비되어 컬렉션에 들어가니(add) 소비자 관점이라고 보는것 같다.

그래서 이 경우에는 super를 사용해주면 되겠다.

휴..되게 어렵다 😇😇😇

아무튼 읽기전용으로 만들고 싶을때에는 extends를 사용하는것.

좀더 안전하게 데이터 삽입을 하고싶다면 super를 사용하는 것만 기억하면 될 것 같다.

728x90

'Java' 카테고리의 다른 글

참조 유형  (0) 2022.09.12
Checked Exception, Unchecked Exception  (0) 2022.09.07
일급 컬렉션  (0) 2022.08.09
변수  (0) 2022.08.07
728x90

Jenkins

위키백과에서 발췌한 내용에 따른다.
젠킨스(Jenkins)는 소프트웨어 개발 시 지속적 통합(continuous integration) 서비스를 제공하는 툴이다. 다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을 방지하기 위해 각자 작업한 내용을 공유 영역에 있는 Git등의 저장소에 빈번히 업로드함으로써 지속적 통합이 가능하도록 해 준다. MIT 라이선스를 따른다.

발생 시점

현재의 회사에서 배포를 젠킨스를 이용하여 배포를 진행한다.
신규 기능개발과 레거시를 청산하는 작업을 주로 해왔었어서 이쪽을 고치는게 우선은 아니었다.
그래서 모르고 있었던 것일수 있다.
에러 상황을 확인해보자

에러 상세

[Pipeline] End of Pipeline
java.lang.InterruptedException
    at java.base/java.lang.Object.wait(Native Method)
    at java.base/java.lang.Thread.join(Thread.java:1300)
    at java.base/java.lang.Thread.join(Thread.java:1375)
    at java.base/jdk.internal.reflect.GeneratedMethodAccessor774.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.InvokerHelper.invokePojoMethod(InvokerHelper.java:913)
    at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:904)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:168)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSafe(ScriptBytecodeAdapter.java:176)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodNSpreadSafe(ScriptBytecodeAdapter.java:183)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0SpreadSafe(ScriptBytecodeAdapter.java:198)
    at org.hidetake.groovy.ssh.interaction.Interactions.waitForEndOfStream(Interactions.groovy:97)
    at org.hidetake.groovy.ssh.interaction.Interactions$waitForEndOfStream$2.call(Unknown Source)
    at org.hidetake.groovy.ssh.operation.Command.execute(Command.groovy:83)
    at org.hidetake.groovy.ssh.operation.Operation$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.execution.Command$Helper.execute(Command.groovy:50)
    at jdk.internal.reflect.GeneratedMethodAccessor951.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite$StaticMetaMethodSiteNoUnwrapNoCoerce.invoke(StaticMetaMethodSite.java:151)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.call(StaticMetaMethodSite.java:91)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy:30)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute$0.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor949.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSite.invoke(PogoMetaMethodSite.java:169)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:71)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper.execute(Command.groovy)
    at org.hidetake.groovy.ssh.session.execution.Command$Trait$Helper$execute.call(Unknown Source)
    at org.hidetake.groovy.ssh.session.SessionHandler.execute(SessionHandler.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor948.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy:182)
    at org.jenkinsci.plugins.sshsteps.SSHService$_executeCommand_closure3$_closure13.doCall(SSHService.groovy)
    at jdk.internal.reflect.GeneratedMethodAccessor947.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57)
    at org.hidetake.groovy.ssh.util.Utility.callWithDelegate(Utility.groovy:17)
    at jdk.internal.reflect.GeneratedMethodAccessor427.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:46)
    at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:102)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:214)
    at org.hidetake.groovy.ssh.session.SessionTask.wetRun(SessionTask.groovy:64)
    at jdk.internal.reflect.GeneratedMethodAccessor6430.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
    at org.hidetake.groovy.ssh.session.SessionTask.call(SessionTask.groovy:48)
    at java_util_concurrent_Callable$call.call(Unknown Source)
    at org.hidetake.groovy.ssh.core.Service.run(Service.groovy:81)
    at org.hidetake.groovy.ssh.core.Service$run$1.call(Unknown Source)
    at org.jenkinsci.plugins.sshsteps.SSHService.executeCommand(SSHService.groovy:177)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution$CommandCallable.execute(CommandStep.java:84)
    at org.jenkinsci.plugins.sshsteps.util.SSHMasterToSlaveCallable.call(SSHMasterToSlaveCallable.java:32)
    at hudson.remoting.LocalChannel.call(LocalChannel.java:46)
    at org.jenkinsci.plugins.sshsteps.steps.CommandStep$Execution.run(CommandStep.java:72)
    at org.jenkinsci.plugins.sshsteps.util.SSHStepExecution.lambda$start$0(SSHStepExecution.java:84)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: ABORTED

정확히 이 에러 윗부분까지는 클라이언트의 요청을 받아서 처리해주고 있다가
한방에 서버가 다운되어버렸다.
젠킨스에서의 배포 스크립트에도 문제가 있었다.
아래의 쉘 스크립트를 젠킨스가 도커 컨테이너 내부에서 실행해주게 만들었다.

#!/bin/bash
export JAVA_TOOL_OPTIONS="-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul"
kill -9 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -9 $(lsof -t -i:8080)
mvn clean package -P dev

java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar

이러한 구성으로 된 쉘 스크립트를 통해 실행을 진행했기 때문에
jar를 즉각 실행하게 만들어서 로그가 그대로 젠킨스에 전부 찍히고,

왜? 🤔

그야 당연할것인데, 로그를 실시간으로 젠킨스가 배포 과정을 찍을텐데,

java -jar 명령어를 백그라운드로 실행시켜주지를 않았다.

젠킨스의 배포는 항상 finished상태가 나오질 않는 상태였다.

항상 이상태였다 ㅋㅋㅋ

이미지

gif 처음만들어봤는데 재밌네..
이부분에서 로그가 과다하게 많이 쌓이게 되어 에러를 내뱉고
was가 죽어버린 상태가 되어버렸다. (비정상적 셧다운)

해결

JENKINS-45150 large console logging can take Jenkins down or affecting performance - Jenkins Jira
검색을 진행해보니 위와같은 내용들도 얻을 수 있었다.
그래서 해결과정의 순서를 생각한 방식은 다음과 같다.

  1. 젠킨스는 배포를 끝내서
    이러한 화면을 만들어주어야 한다.

  2. 스프링 애플리케이션의 배포 스크립트를 바꿔주어야 한다. (java -jar를 백그라운드로)

이렇게 하면 되겠다!

그래서 전부 바꿔주게 된다.

#!/bin/bash
export JAVA_TOOL_OPTIONS="-Dfile.encoding='UTF8' -Duser.timezone=Asia/Seoul"
kill -15 $(lsof -t -i:8080)
cd /root/server/
git pull
git checkout $1
git pull origin $1
kill -15 $(lsof -t -i:8080)
mvn clean package -P dev

nohup java -jar -Dspring.profiles.active=dev target/server-0.0.1-SNAPSHOT.jar > ~/app.log 2>&1 &

echo "Deploy Success"

nohup을 이용한 중단없이 실행해주고 젠킨스는 밖으로 빠져나와야 했기 때문에
이 명령어를 선택하고 실행해주었다.

그와 동시에 간단하게 라이브로 볼수있게끔 기본적으로 생성되는 nohup.out을 혹시몰라 만들어둔채로 마무리를 해놓았다.

그리고 kill명령어를 15로 바꾸었는데,
9는 강제종료기 때문에 진행중이던 작업을 즉시 종료하고 데이터도 저장하지 않는다.
15는 자신이 하던 작업을 모두 안전하게 종료하는 절차를 밟는다.
메모리상에 있는 데이터와 각종 설정/환경 파일을 안전하게 저장한 후 프로세스를 종료한다.
15로 한다고 한들, 종료 명령어를 주게되면, 어떤 클라이언트가 요청을 보내서 작업중인 데이터도 끊어질 것이다.
그래서 spring에서 제공하는 graceful shutdown을 적용하고 kill -15를 같이 붙여주었다.
graceful shutdown은 지금 포스팅에서 다루지 않겠다.
이렇게 해서 젠킨스의 로그 과다 적재로 서버가 죽는 현상을 제거시키게 되었다.

정리

이렇게 되어 젠킨스에 기존에 (내가 건드리지 않은) 잘못되게 설정되어 있던 것을 고치게 되니
괜찮다.
그러면서 동시에 툴도 툴마다의 각자의 할일이 있는 것인데,
CI/CD를 위한 툴에서 로그 모니터링까지 하고 있었으니 과다적재로 에러를 뱉는다는 것은
어찌보면 당연한 것이었을 수 있다고 나는 생각한다.🔥

728x90

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

FeignClient Logging level 디버깅  (0) 2022.12.17
@Async 사용시 에러 해결  (0) 2022.11.04
AWS SNS 토큰 에러  (0) 2022.08.10
YAML 파일을 읽어보자  (0) 2022.08.09

+ Recent posts