프로그래밍을 배우기 시작한지 딱 1년 정도가 지났다. 42Ecole의 교육 과정이 한국에서도 진행된다고 했을 때, 지금이 기회라고 생각했다. 더 늦기 전에 내가 하고 싶은 것을 하기 위해 잘 다니고 있던 회사도 고민없이 그만두었다. 피신 시작 기간에 맞춰 퇴사했지만, 코로나로 인해 계속 지연되는 피신으로 인해 살짝 불안하기도 했었다. 다행히 5월에 피신을 시작할 수 있었고, 한 달 간 코딩, 밥, 잠 이외에는 아무것도 하지 않았다고 해도 과언이 아닐 정도로 몰두했다. 그 결과 본 과정에 합격했고, 여러 과제를 진행하면서 현재까지 무사히 잘 살아남아 있다.

 

피신을 시작하기 전엔 피신을 하면서 다 배울 수 있다고 해서 정말 아무런 준비도 하지 않고 피신을 시작했다. 첫 날에는 C언어가 아닌 난생 처음 만져보는 shell에 당황했다. 처음엔 이런 것도 배워야 하나? 라고 생각했는데, 지금 돌아보면 정말 좋은 출발이었던 것 같다. 오히려 지금은 GUI보다 CLI가 더 편할 때도 있다.

 

두 개의 shell 과제가 끝나고 C언어 과제를 시작했을 때 역시 당황하지 않을 수 없었다. "라이브러리를 쓸 수 없다고...? 그럼 어떻게 하라는 거지? 출력하는 다른 방법이 있나?" 한참을 고민하고 있을 때, 슬랙에 글 하나가 올라왔다. "Linux에서는 fd라는게 있는데 ~~~" 아직도 잊혀지지 않는 그 글의 내용은 바로 file descripter에 관한 내용이었다. 아마 이것이 내가 피신에서 배운 가장 첫 컴퓨터공학 지식이었던 것 같다. 그 이후로도 슬랙엔 피신을 진행하면서 많은 정보들이 공유되었고, 모르는 것은 직접 그 글을 올린 사람을 찾아가 물어보기도 했다.

 

피신에서 가장 좋았던 점은 지식과 정보의 나눔이었던 것 같다. 내가 모르는 건 가서 물어보고, 내가 아는 건 알려주는 그 과정이 너무 좋았다. 누군가 과제에 대해 고민하고 있으면 그 과제를 해결한 사람들이 너 나 할 것 없이 몰려가서 알려주곤 했다. 같은 과제를 하고 있는 사람이면 지금 어떤 방법으로 문제를 해결하고 있는지, 어떤 방법이 더 좋은지, 그런 얘기들을 나누며 과제를 하나씩 해결해나갔다. 뿐만 아니라, 내가 아직 진행하지 않은 과제에 대한 설명을 듣기 위해 일부러 평가를 진행한 경우도 자주 있었다.

 

개인적인 감상으로는 1기 2차 피시너들간의 교류가 유독 활발했던 것 같다. 처음에 피신 예정 인원은 150명 정도였는데, 코로나로 인하여 3개월 정도 연기되면서 약 90명 정도 밖에 남지 않았고, 따라서 거의 모든 피시너들을 알 수 있을 정도였다. 그래서 남은 소수 인원들 끼리 같이 잘해보자라는 마인드로 피신에 임해서 그랬던 것 같다.

 

올해 초 까지만 해도 본과정을 진행하면서 과연 내가 제대로 학습하고 있는 게 맞는지 의문이 들 때가 많았다. 그러나 최근에 기초를 공부하기 위해 운영체제 강의를 보면서 지금까지 학습했던 개념들이 나오는 것을 보고 잘 하고 있다는 확신이 들었다. 과제를 진행하면서 겪었던 각종 에러나 문제를 해결하는 과정들이 전부 컴퓨터공학 지식을 학습하는 과정이었던 것이다. 이 사실을 깨닫고 나서는 동료평가를 하는 기준이 조금 달라진 것 같다.

 

이전까지의 동료평가는 단순히 코드를 얼마나 깔끔하게 짰는지, 과제에서 요구하는 사항을 잘 만족하는지 그저 주어진 조건에만 맞게 평가를 진행했었다. 그러나 과제를 해결하는 과정이 컴퓨터공학 지식을 학습하는 과정이라는 것을 깨달은 이후에는 그 과제가 어떤 지식을 학습시키길 원하는가를 생각해보게 되었다. 예를 들어, 가장 첫 과제인 libft같은 경우는 그냥 단순히 라이브러리 함수를 똑같이 구현하는 과제가 아닌, 함수의 동작이 어떻게 이루어지고, 각각의 파라미터나 변수들이 어떤 것을 의미하는지 생각해보는 과제라고 생각한다.

 

예를 들어, libft를 평가하면서 항상 하는 질문은 다음과 같다.

#include <stdio.h>

int main()
{
        char *str;
        str = NULL;
        printf("%d\n", ft_strlen(str));

        return 0;
}

테스터를 모두 통과해도 위 코드를 실행시키면 segfault가 발생한다. 평가를 받는 사람은 자신의 코드가 잘못된 줄 알고 당황한다. 그러나 코드는 잘못되지 않았다. 그렇다면 왜 segfault가 발생하는가? 답은 간단하다. 라이브러리의 strlen 함수도 마찬가지로 segfault가 발생한다. 그냥 strlen 함수가 그렇게 만들어져있기 때문이다. str이 null pointer일 때의 동작이 정의되지 않아서 에러가 발생하는 것이다. 지금까지 평가를 진행하면서 이 질문에 대한 답변을 제대로 들은 경우는 많지 않았다. 아마 직접 테스트 해보지 않고 단순히 테스터에만 의존하여 과제를 해결했기 때문이 아닐까 생각한다.

 

테스터가 나쁘다는 것은 아니다. 테스터가 많은 부분에서 이점을 주는 것은 사실이다. 30분이라는 제한된 시간 동안 모든 함수가 제대로 동작하는지 각종 테스트케이스를 넣어보면서 확인하긴 어렵기 때문에 테스터를 사용하는 것은 큰 도움이 된다. 다만 테스터는 어디까지나 보조수단일 뿐, 테스터를 너무 맹신하지 않는 것이 좋다고 생각한다. 물론 나도 과거에는 이러한 부분들을 반성하고 있고, 지금은 테스터 결과와는 상관 없이 그 사람이 얼마나 과제를 이해하고 코드를 짰는지를 더 중점적으로 보는 편이다.

 

이전에 코로나로 클러스터 출입이 불가하여 원격으로 ft_server 과제를 평가했던 적이 있었다. 평가를 받으시는 분은 VNC 화면을 공유하여 평가를 진행하는데, 자꾸 알트탭으로 창을 전환하며 어딘가에 적혀진 글을 보면서 명령어를 입력하는 것이 보였다. 그래서 피평가자분께 혹시라도 치팅으로 의심될 수 있으니 해당 글을 보지 말고 평가를 진행하기를 요청드렸더니 명령어를 모른다고 하셔서 더 이상 평가를 진행할 수 없었다. 과제 평가 기준에는 부합하지만 내가 생각했을 때 Docker 명령어도 모르고 이 과제를 했다는 것은 이 과제에서 요구하는 바를 만족시키지 못했다고 설명하고 Fail을 드렸다. 평가가 끝난 후 피평가자분께서 "안일한 생각으로 과제 평가표만 넘길 정도로 과제를 진행한 자신에 대해 부끄럽고 죄송하다"며 DM을 보내주셨고, 잘 마무리 되었다.

 

그러나 항상 이렇게 훈훈한 평가만 있는 것은 아니었다. 과제 관련 내용을 질문했을 때, 해당 내용은 평가 기준이랑 관계 없지 않냐며 짜증을 내는 사람 뿐만 아니라 평가 기준을 다 만족하는데 왜 Fail 이냐고 따지는 피평가자도 일부 있었다. 해당 내용이 왜 평가랑 관계가 있는지, 설명한 후에 Fail을 드리고 나면 평가 Feedback 점수를 0점으로 테러하는 사람도 있었다. 어쩌면 다른 사람에게 평가받았으면 통과했을지도 모르지만, 과제가 요구하는 바를 깨달아버린 이상 어쩔 수 없다. 운도 실력이라고 하지 않았는가!

 

회고를 쓰다 보니 잠시 딴 길로 샜는데, 어쨌든 요점은 평가를 통과하기 위한 과제 진행은 지양하는 것이 바람직하다고 생각한다. 1년쯤 하고 보니 42는 정말 내가 노력하는 만큼 알게되고, 내가 아는 만큼 더 많은 것을 보고 배울 수 있다는 것을 느꼈다. 지금도 평가를 진행하면서 이미 지나온 과제이지만 나도 간과하고 넘어간 부분이 있기 때문에 끝난 과제에서도 배우는 것이 많다.

 

김수보 멘토님의 글에서도 알 수 있다시피, 우리는 기술을 배우는 것이 아니다. 문제를 풀고, 협력하고, 스스로를 가르치고, 창의적이고, 비판적 사고를 가지기 위한 수용능력을 개발하는 것이다. 그러기 위해서 라이브러리를 쓰지 않고 과제를 해결하는 것이고, 과제를 해결하는 것이 곧 문제를 해결하는 능력을 기르는 것이라고 생각한다. 나는 이 사실을 깨닫는데 1년이라는 시간이 걸린 것 같다.

 

1년이라는 시간이 생각보다 빠른 것 같다. 1년동안 그토록 좋아하던 게임을 거의 안했는데, 코딩하고 공부하는 게 재밌었기 때문에 게임 생각이 안 나서 참 다행인 것 같다. 최근에는 AIFFEL이라는 인공지능 관련 교육을 듣느라 42과정을 잠시 미뤄두긴 했지만, 다시 42과정에 집중할 생각이다. 이제 사람도 많아진 만큼 나보다 과제를 앞선 사람들도 많아졌으므로 내가 부족한 부분을 잘 찝어줄 것이라 생각한다. 얼른 cpp 해야지.

'잡담' 카테고리의 다른 글

2021년 회고  (0) 2022.01.01
첫 입사, 수습기간, 정직원  (2) 2021.11.23
개발자로서의 첫 이력서 작성기  (0) 2021.03.07

  오늘 아침부터 오종인 멘토님과 몇 명의 카뎃들과 함께 개발자 이력서를 작성해보는 '켠김에 이력서까지'를 진행했다. 신청했을 당시에는 내가 뭘 한 게 없는데 이력서를 쓸 수 있나? 라고 생각했었는데, 정답이었다. 나는 정말 아무것도 한 게 없었던 것이다.

 

  먼저, 오전 10시부터 시작하여 한 시간 정도 이력서를 작성해본 후 피드백을 받기로 하였다. 지금까지 그래왔듯이 가벼운 마음으로 이력서를 작성하였다. 핵심 역량에는 평소에 내가 잘 한다고 생각했던 것들을 적었다. 이전 직장의 경력이 기술영업 직군이어서 쓸지 말지 고민했지만, 일단 쓰기로 했다. 쓰다보니 금세 한 시간이 지나 피드백을 받았다. 결과는 너무 탈탈 털려서 뼈는 커녕 살까지 분쇄되어 다짐육이 될 정도였다. 내가 적응력이 뛰어난 걸 증명할 수 있는가? 뛰어난 같은 수식어구가 굳이 필요한가? 다양한 개발 언어 활용 능력이 중요한가? 원만한 성격인 것은 어떻게 증명할 수 있는가?

 

  핵심 역량은 내가 증명할 수 없는 것이면 쓰지 않는 편이 좋다고 한다. 생각해보니 위 질문들에 대해 한 번도 대답을 생각해본 적이 없는 것 같다. 내가 잘 하는 것을 어떻게 증명해야 하는가. 답은 간단하다. 증명 가능한 사실만 적으면 되는 것이다. 본인의 어떤 경험을 살려 해당 역량을 증명할 수 있으면 적으면 된다.

 

  나는 영업직으로 일했던 경험이 커뮤니케이션 능력을 증명해줄 수 있다고 생각했는데 전혀 아니었다. 오히려 영업직이라고 하면 자신의 실적을 위해 수단과 방법을 가리지 않는 사람이라는 부정적인 시선으로 볼 수 있다는 이야기를 들었다. 물론 내가 실제로 그런 사람은 아니지만, 그런 생각을 가진 사람도 있을 수 있다는 생각에 말문이 막혔다. 어차피 저는 아닙니다 라고 해봤자 증명할 수도 없고, 변명거리만 될 뿐이니까. 그래서 내가 증명할 수 없는 것들을 모두 빼보았다.

 

텅 빈 이력서

    다 지우고 나니, 이력서가 텅텅 비어버렸다. 그렇다. 정말 아무것도 한 게 없었다는 걸 깨달았다. 그나마 AIFFEL에서 머신러닝 관련해서 배우는 게 있으니 관련 Framework를 쓸 수 있는 정도였다. 그러나 그마저도 AI 엔지니어를 목표로 하기 위해서는 AI 도메인보다 개발 지식이 더 중요한 것이었다.

 

  나도 AIFFEL을 시작하기 전에는 인공지능에 대해 공부하기만 하면 개발을 잘 못해도 설계만 잘 하면 충분히 취업시장에서 경쟁력이 있다고 생각했다. 그러나 공부를 하면 할 수록 인공지능에 대한 지식만 알아서는 할 수 있는게 크게 없다는 것을 깨달았다. 설계를 잘 하는 사람? AI Researcher가 있는데 굳이 AI Engineer를? 딥러닝 모델 설계를 잘 하는 사람은 내가 아니어도 많을 것이다. 딥러닝 모델을 설계하기 이전에 해당 문제를 딥러닝으로 해결할 수 있는지 부터 판단할 수 있어야 하며, 해당 모델을 어디에서 사용할 것인지, 자원은 얼만큼 있는지, 처리 속도는 얼마나 중요한지, 어느 정도의 정확도가 요구되는지 등은 모델만 잘 설계한다고 해서 알 수 있는 것들이 아니었다. 따라서 인공지능에 대해 공부하기 이전에 개발 지식을 먼저 습득하고, 해당 분야의 도메인을 어느 정도 파악한 후에 해당 문제를 딥러닝으로 해결할 수 있는지를 판단할 줄 알아야 하는 것이다. 이 부분은 인지하고 있었지만 멘토님께 팩트로 맞음 당해서 다시 한 번 깨닫게 되었다.

 

   프로젝트의 중요성에 대해서도 다시 깨닫게 되었다. 같이 켠김에 이력서를 진행했던 hyukim님의 이력서도 보게되었는데, 그 동안 진행했던 것들이 노션에 잘 정리되어 있었다. 그 노션을 보고 2차로 스스로 맞음 당했다. 그동안 진행했던 프로젝트들이 잘 정리되어 있었고(내 기준), 그걸 보고 더 열심히 해야겠다는 생각이 들었다. 물론 지금도 열심히 안 하고 있었던 것은 아니지만 아무튼 더 열심히 해야 한다고 생각했다. 멘토님께서는 프로젝트에서 뭘 했는지 보다 해당 프로젝트를 진행하면서 어떤 기술을 써봤고, 왜 이 기술을 사용했으며, 거기서 무엇을 배웠는지를 잘 정리하는게 중요하다고 하셨다. 이런 부분은 나중에 프로젝트를 할 때 까먹지 말고 잘 적어두어야겠다.

 

  결국 나는 하루 종일 후드려 맞음 당하기만 했으나, 자기 반성의 시간을 가지면서 내가 앞으로 무엇을 해야 할지에 대한 방향성을 정하는 날이었다. 비록 지금은 아무것도 적혀진 게 없는 null뿐인 이력서지만, 추후에는 멘토님께서 기업에 '이 카뎃 잘해요' 라고 당당하게 추천할 수 있는 이력서를 만들어야겠다.

 

 

카뎃 여러분, 이력서 꼭 미리미리 준비합시다!

'잡담' 카테고리의 다른 글

2021년 회고  (0) 2022.01.01
첫 입사, 수습기간, 정직원  (2) 2021.11.23
1년간의 회고  (0) 2021.05.20

들어가며

이 글은 42seoul의 libasm 과제를 해결하기 위한 자료로 작성되었습니다.

참고문서(ryukim) https://www.notion.so/Libasm-3c94bbc7df234499b012f6ae82b84dc2

작동 환경 : masOS(Catalina) 10.15.5


목차

  1. nasm 설치

  2. Hello World 출력해보기

  3. Mendatory Part

  4. Bonus Part


  1. nasm 설치

    • MacOS 에는 기본적으로 nasm 이 설치되어 있지 않다. 따라서 우선 nasm 을 설치해줘야 한다.
    • MacOS에서 nasm의 설치는 nasm 공식 홈페이지에서 압축파일을 직접 다운받아 local에 설치하는 방법과 Homebrew를 이용하여 설치하는 방법 두 가지가 있다.

    1.1 공식 사이트에서 파일을 다운받아 설치하기

    • NASM(https://www.nasm.us/) 웹사이트에서 최신버전을 확인한다.
    • nasm-x.xx.xx-tar.gz 형태의 최신 버전을 받아서 압축을 해제한다.
    • nasm-x.xx.xx 폴더에서 ./configure 명령어를 입력한다. 해당 명령어는 Makefile을 이용하여 적절한 C 컴파일러를 찾아주는 명령어이다.
    • make 명령어를 입력하여 nasm을 build 해준다.
    • make install 명령어를 입력하여 nasm을 설치한 후, man page를 확인하여 설치된 것을 확인한다.

    1.2 Homebrew를 이용하여 설치하기

    • 우선 ruby -e "$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/master/install](https://raw.githubusercontent.com/Homebrew/install/master/install))" < /dev/null 2> /dev/null 명령어로 Homebrew를 설치한다.
    • 그 다음 brew install nasm 으로 nasm을 설치한다.
      • permission 오류가 발생할 경우, chown 명령어로 해당 경로에 권한을 주어 설치될 수 있도록 한다. (참고)
    • nasm -ver 명령어를 이용하여 nasm의 버전을 확인할 수 있다.
  2. Hello World 출력해보기

    • hello_world.s 파일을 만들고 다음과 같이 입력한다.

        global    start
            section   .text
        start:
            mov       rax, 0x02000004
            mov       rdi, 1
            mov       rsi, message
            mov       rdx, 13
            syscall
            mov       rax, 0x02000001
            xor       rdi, rdi
            syscall 
            section   .data
        message:  
            db        "Hello, World", 10
    • nasm 을 이용하여 object 파일을 생성해준다.

        nasm -f macho64 hello_world.s
    • linker를 이용하여 실행할 수 있는 파일로 만들어준다.

        ld -macosx_version_min 10.7.0 -o hello_world hello_world.o

      -macosx_version_min flag를 넣어주는 이유

    • hello_world 를 실행해보자.

        ./hello_world

      https://s3-us-west-2.amazonaws.com/secure.notion-static.com/acb506fe-67eb-407f-b712-41279f96fa41/Untitled.png

    • global _main 변수로 linking할 경우 ld -lSystem flag를 사용해야 linking 가능하다.

  3. Mandatory Part

     int        ft_strlen(char const *str);
     int        ft_strcmp(char const *s1, char const *s2);
     char        *ft_strcpy(char *dst, char const *src);
     ssize_t        ft_write(int fd, void const *buf, size_t nbyte);
     ssize_t        ft_read(int fd, void *buf, size_t nbyte);
     char        *ft_strdup(char const *s1);
    • ft_strlen
      • 64bit macOS에서 parameter는 rdi, rsi, rdx, rcx, r8, r9 register를 통해서 전달되며, 그 이상의 parameter는 stack을 통해 전달된다. 따라서, char const *str은 rdi에 전달되어 있다. 관련 자료는 calling convention을 찾아보자. 참고자료
      • rax에 저장되어 있는 값은 함수가 끝났을 때 return하는 값이기 때문에 count index를 rax로 사용하였다.
      • strlen 함수는 문자열의 길이를 반환하는 함수이므로, str의 주소를 참조하여 해당 문자가 null인지 아닌지 판단하여 값을 반환하게 된다.
      • assembly에서 값을 비교할 때는 register의 크기에 주의해야 한다. 문자 하나의 data size는 1byte이기 때문에 null을 판단할 때에도 byte 만큼만 비교해야 한다.
    • ft_strcmp
      • assembly에서는 두 operand를 비교할 때, 둘 중 하나의 operand에만 추가 연산이 가능하다. 예를 들어, a와 b를 index 만큼 이동한 위치를 비교한다고 가정할 때, a + index, b + index 와 같은 연산이 불가능하다는 이야기이다.
      • 따라서 위와 같은 연산을 할 경우에는 다른 register에 값을 옮긴 후 옮겨진 값을 이용하여 연산해야 한다.
      • libft에서 고려하지 않았던 확장 아스키코드와의 비교도 가능하기 때문에 unsigned char의 범위 안에 있는 문자는 모두 비교할 수 있어야 한다.
      • 문자의 차이값을 구하는 과정에서 특정 register의 flag에 주의하여야 한다. overflow가 발생했을 경우에 어떻게 처리해야 할지 잘 생각해봐야 한다.
    • ft_strcpy
      • ft_strcmp와 마찬가지로 하나의 register를 temp로 사용하여 src에 있는 값을 복사한 후 복사된 값이 null인지를 체크하여 마지막에 *dest를 return 해주면 된다.
      • 이 함수를 작성하면서 왜 parameter에서 dest가 먼저 전달되는 지 알 수 있었다. rdi와 rsi의 관계가 dest와 src의 관계와 일치한다.
    • ft_write
      • syscall에 대해 이해하고 있다면 함수를 작성하는 것 자체는 어렵지 않다. 그러나, error처리에 대한 부분을 간과하기 쉽다.
      • syscall 후에 error가 발생했다면, 자동적으로 error number를 rax에 return해준다.
      • error가 발생했을 때, 적절한 return value와 errno를 반환해야 하기 때문에 먼저 ___error를 불러오는 방법부터 알아야 한다.
      • ___error 함수를 호출했을 때, return되는 값은 errno의 주소값이다.
      • errno가 제대로 설정되었는지 확인하려면 standard 함수에서의 errno와 비교해보면 된다. 다만 standard 함수에서 errno 출력 후, ft_write 함수를 불러오기 전에 errno를 다시 초기화 해주어야 하는 것을 잊지 말자.
      • https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master syscall 목록
    • ft_read
      • syscall 해야 하는 코드만 다를 뿐, ft_write와 거의 동일하다.
    • ft_strdup
      • ft_strlen함수로 strlen을 구한 후 + 1만큼을 malloc하여 ft_strcpy를 이용하여 복사해주면 쉽게 해결할 수 있다.
      • 다른 함수를 호출할 때, caller, callee calling convention에 유의해야 한다.
  4. Bonus Part

     typedef struct    s_list
     {
         void                    *data;
         struct s_list    *next;
     }                                t_list;
    
     int            ft_atoi_base(char const *str, char const *base);
     void        ft_list_push_front(t_list **begin_list, void *data);
     int            ft_list_size(t_list *begin_list);
     void        ft_list_sort(t_list **begin_list,int (*cmp)());
     void        ft_list_remove_if(t_list **begin_list, void *data_ref, int (*cmp)(), void (*free_fct)(void *));
    • ft_atoi_base
      • ft_atoi_base는 piscine때 했었던 함수와 동일하게 작성하면 된다.
      • str에 있는 숫자를 base에 맞는 진법으로 변환하여 출력하는 문제이다.
      • str에서 처리해야 할 문자 순서는 whitespace→sign→number순이다.
      • 나머지 규칙은 piscine의 subject를 보고 해당 규칙에 맞게 작성하면 된다.
    • ft_list_push_front
      • assembly에서 list를 구현하기 위해서는 우선 linked list가 어떠한 구조로 이루어져 있는지 이해할 필요가 있다.
      • 구조체에 선언된 data type의 size만큼 메모리를 차지하기 때문에 구조체의 메모리 주소로부터 + datasize만큼 이동한다면 해당 data의 주소를 얻을 수 있다.
      • linked list에서 list의 위치를 조작할 경우에 반드시 이전 list의 주소를 잃어버리지 않도록 기록해두어야 한다.
    • ft_list_size
      • ft_list_size함수는 ft_strlen에서 배열 대신에 list로 바뀌었을 뿐이다.
      • list→next 가 null 인지 확인한다.
    • ft_list_sort
      • ft_list_sort 함수는 piscine 때의 함수와 같은 함수를 assembly로 작성하는 것이다.
      • list를 정렬할 때, 맨 첫 부분, 중간 부분, 마지막 부분으로 나누어서 swap을 진행하였다.
      • swap algorithm은 bubble sort를 사용하였다.
      • 정 안되겠을 땐, C로 작성한 후에 옮겨보자.
    • ft_list_remove_if
      • ft_list_remove_if 함수는 list에서 data와 일치하는 값을 제거 하는 함수이다.
      • 값을 제거한 후 list를 다시 연결해주어야 하기 때문에 제거되는 위치를 고려해주어야 한다.
      • 비교할 datasize에 유의하기
  • threads 에 대하여 배우는 과제
  • mutex, semaphore

Mandatory Part


  • C로 짜야 하고, norm도 맞춰야 한다. 암튼 터지면 안됨

  • 테이블에 둘러앉은 철학자들은 3가지를 한다. 먹고, 생각하고, 자기

  • 먹는 동안은 생각하거나 잘 수 없고, 자는 동안엔 먹거나 생각할 수 없고, 생각하는 동안엔 먹거나 잘 수 없다.

  • 철학자들은 테이블에 원형으로 앉아있고, 가운데는 큰 스파게티가 있다.

  • 몇 개의 포크가 테이블에 있다.

  • 스파게티는 포크 하나로 못 먹기 때문에 철학자들은 반드시 한 손에 포크 하나씩 포크 두 개를 사용해야 한다.

  • 철학자는 굶어선 안된다.

  • 모든 철학자는 먹어야한다.

  • 철학자는 다른 철학자와 얘기하지 않는다.

  • 철학자는 다른 철학자가 언제 죽을지 모른다.

  • 철학자는 식사가 끝날 때 마다 포크를 놓고 잔다.

  • 철학자는 자고 일어나면 생각한다.

  • 철학자가 죽으면 시뮬레이션이 끝난다.

  • 각 프로그램은 같은 옵션을 써야 한다.

    number_of_philosophers : 철학자와 포크의 수

    time_to_die : 단위는 ms이며, 시뮬레이션이 시작하거나 마지막 식사 이후 'time_to_die' 안에 먹는 것을 시작하지 못하면 철학자는 죽는다.

    time_to_eat : 철학자가 먹는데 걸리는 시간이며, 먹는 동안에는 포크 2개를 유지한다.

    time_to_sleep : 철학자가 자는데 쓰는 시간

    number_of_times_each_philosopher_must_eat : 이 argument 는 선택사항이다. 시뮬레이션이 멈추기 위한 최소 식사 횟수 조건. 만약 정의되지 않으면 철학자가 죽을 때 시뮬레이션이 끝난다.

  • 각 철학자는 1부터 number_of_philosophers 까지 숫자가 주어진다.

  • 1번 철학자 옆에는 number_of_philosophers 숫자의 철학자가 있으며, N번째 철학자 옆에는 N-1과 N+1이 있다.

  • 철학자의 어떤 상태 변화든 아래 규칙을 따라 쓰여져야 한다. (X는 철학자 숫자)

    • timestamp_in_ms X has taken a fork
    • timestamp_in_ms X is eating
    • timestamp_in_ms X is sleeping
    • timestamp_in_ms X is thinking
    • timestamp_in_ms X died
  • 상태 출력할 때 다른 철학자랑 섞이게 하지 마라

  • 철학자의 죽음과 철학자가 죽은 것을 출력할 때 10ms 이상 넘기면 안된다.

  • 철학자를 죽이지 마라!

함수 정리

  • usleep()

      #include <unistd.h>
      int usleep(useconds_t useconds);
    
      microseconds 단위로 함수를 대기시키는 함수
      1ms = 1000us
  • gettimeofday()

      #include <sys/time.h>
      int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
    
      struct timeval start, end;
      double diff;
      gettimeofday(&start, NULL);
      usleep(1000)
      gettimeofday(&end, NULL);
      diff = (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_usec - start.tv_usec) / 1000.0
      printf("%f\n", diff);
    
  • pthread https://reakwon.tistory.com/56

    • pthread_create

        int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
      
        1. thread : 성공적으로 함수가 호출되면 이곳에 thread ID가 저장됩니다. 이 인자로 넘어온 값을 통해서 pthread_join과 같은 함수를 사용할 수 있습니다.
        2. attr : 스레드의 특성을 정의합니다. 기본적으로 NULL을 지정합니다. 만약 스레드의 속성을 지정하려고 한다면 pthread_attr_init등의 함수로 초기화해야합니다.
        3. start_routine : 어떤 로직을 할지 함수 포인터를 매개변수로 받습니다. 
        4. arg : start_routine에 전달될 인자를 말합니다. start_routine에서 이 인자를 변환하여 사용합니다.
    • pthread_join

        int pthread_join(pthread_t thread, void **retval)
      
        1. thread : 우리가 join하려고 하는 thread를 명시해줍니다. pthread_create에서 첫번째 인자가 있었죠? 그 스레드가 join하길 원한다면 이 인자로 넘겨주면 됩니다.
        2. retval : pthread_create에서 start_routine이 반환하는 반환값을 여기에 저장합니다.
    • pthread_detach

      때에 따라서는 스레드가 독립적으로 동작하길 원할 수도 있습니다. 단지 pthread_create 후에 pthread_join으로 기다리지 않구요. 나는 기다려주지 않으니 끝나면 알아서 끝내도록 하라는 방식입니다.

      독립적인 동작을 하는 대신에 스레드가 끝이나면 반드시 자원을 반환시켜야합니다. pthread_create만으로 스레드를 생성하면 루틴이 끝나서도 자원이 반환되지 않습니다. 그러한 문제점을 해결해주는 함수가 바로 pthread_detach입니다.

        int pthread_detach(pthread_t thread)
      
        thread는 우리가 detach 시킬 스레드입니다. 
        성공시 0을 반환하며 실패시 오류 넘버를 반환하지요.
        pthread_detach와 pthread_join을 동시에 사용할 수는 없습니다.
  • mutex https://reakwon.tistory.com/98

    • pthread_mutex_init

      1) 정적으로 할당된 뮤텍스를 초기화하려면 PTHREAD_MUTEX_INITIALIZER 상수를 이용해서 초기화합니다.

      이런 형식으로 사용합니다. : pthread_mutex_t lock = PTHREAD_MUTX_INITIALIZER;

      2) 동적으로 초기화하려면 pthread_mutex_init 함수를 사용하면 됩니다. mutex를 사용하기 전에 초기화를 시작해야합니다.

        int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    • pthread_mutex_lock

      pthread_mutex_lock, pthread_mutex_unlock : 이 두 함수는 mutex를 이용하여 임계 구역을 진입할때 그 코드 구역을 잠그고 다시 임계 구역이 끝날때 다시 풀어 다음 스레드가 진입할 수 있도록 합니다.

        int pthread_mutex_lock(pthread_mutex_t *mutex);
    • pthread_mutex_unlock

        int pthread_mutex_unlock(pthread_mutex_t *mutex);
    • pthread_mutex_destroy

        int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • semaphore https://yechoi.tistory.com/55 https://noel-embedded.tistory.com/732

    • sem_open

        #include <fcntl.h>           /* For O_* constants */
        #include <sys/stat.h>        /* For mode constants */
        #include <semaphore.h>
      
        sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
      
        oflag = O_CREAT
        mode = 0644
        value = semaphore 수
    • sem_close

        int sem_close(sem_t *sem);
      
        close a named semaphore
    • sem_post

        int sem_post(sem_t *sem);
      
        unlock a semaphore
    • sem_wait

        int sem_wait(sem_t *sem);
      
        lock a semaphore
    • sem_unlink

        int sem_unlink(const char *name);
      
        remove a named semaphore
      
        Ctrl + C 등으로 빠져나왔을 때 name을 가진 semaphore가 그대로 남아있어서 remove 할 때 사용한다.

+ Recent posts