https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/

최근 깃헙 인증 방식이 바뀌어서 SSH 로 인증하려고 했음

결과적으로 로컬 유저와 리모트 유저가 다른 유저 권한으로 저장소에 액세스 하는 문제가 생김

 

해결책은 SSH 키에 -C 옵션과 config 파일을 설정함으로써 해결

ssh-keygen -t rsa -C "github_email_address"

config 파일 내용은 다음과 같음

Host github.com-ke2ek
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_rs

그리고 push 하기 전에 global 설정으로 유저이름과 메일을 설정해주어야 함

git config -l --global 로 확인해보면 다음과 같다. 향후 https 접근을 모두 ssh 로 바꿔주기

url.git@github.com:ke2ek.insteadof=https://github.com/ke2ek
user.name=사용자 이름
user.email=깃헙 메일

 

'Note' 카테고리의 다른 글

자주 사용하는 git 명령어  (0) 2021.02.27
동적 라이브러리 경로 변경  (0) 2019.07.24
[Linux Debugger] strace  (0) 2019.07.03
[linux] python wsgi server, db 배포 이후 (운영, 관리)  (0) 2019.07.01
오픈소스 가이드  (0) 2019.06.30

지원서

중요한 문항은 다음과 같다.

  • 학교 수업 과정에서 의미있었던 과목 5개를 설명
  • 프로젝트 경험 및 문제 해결 방법을 자유롭게 쓰는 것

과목은 A/A+ 받은 전공 과목만 썼고, 프로젝트 경험은 코드까지 붙여가면서 음슴체(...)로 6000자를 채웠다.

  • 서류 합격자들 말에 따르면 1000자도 안 적은 사람들이 있다고 한다.

코딩테스트

이전부터 구글 코드잼 준비 때문에 알고리즘 공부를 하긴 했으나, 국내 대기업들의 코딩 테스트는 구글 것과는 차이가 있다. 수학적인 능력보다 실무에 집중된 문제라는 점이다. 예를 들어, 해시 테이블, 이분 탐색, 슬라이딩 윈도우 등의 문제가 주로 나오며 DFS/BFS나 DP의 비율은 낮은 편이다. 이전 공채에서 투 포인터 알고리즘도 나왔다길래 공부했는데 전혀 도움이 되지 않았다. (언젠간 쓰이겠지..)

따라서, 비슷한 성향이 있는 카카오의 프로그래머스 페이지 Lv3과 Lv4를 2번 정도 풀었다. (풀이를 외웠다고 보는게 맞겠다) 난이도는 비슷하거나 더 낮았는데 마지막 문제는 DFS로 정말 노가다여서 결국 남은 시간 안에 못풀었다. (제출하려고 했는데 끝났다...)

  • 한 문제 못 풀긴 했는데 통과였다. 두 문제 못풀어도 통과한 사람들이 있다고 한다.

1차 면접

우선 블로그에 운영체제/데이터베이스/네트워크/머신러닝 등 정리해놓은 글들이 있는데, 이거를 다시 정리해서 달달달 외웠다. (그냥 버튼 누르면 튀어나오듯이...)

근데 너무 달달외워가서 더 이상 질문을 안하셨다 ㅋㅋㅋㅋ........ (넘하잖아)

면접은 생각보다 어려웠고, 암기해서 내뱉는 기본 지식보다는 프로그래밍을 얼마나 이해하고 있는지 테스트하는 느낌이었다. 그 외에 수학/CS 질문도 받았다.

문제는 알려줄 수 없지만, 본인이 충실하게 기초 수학(필수)과 기초 알고리즘(필수)을 공부했다면 충분히 통과할 수 있는 면접이었다.

  • "모르겠다"는 말을 어떤 면접보다 많이 했었지만, 면접관 분들은 개의치 않고 조금 더 생각해보라고 이야기했다.
  • 정말 말 그대로 "생각해야 하는 문제"를 낸다. 그리고 본인의 논리를 잘 설명해야 통과하는 듯 하다.
  • 인성검사는 답 못하고 넘어간 것도 있는데 (제한시간 넘 적음..) 문제될 사유가 없다면 상관없는 듯 하다.

2차 면접

마지막 면접은 리더급 두 분과 자기소개서 기반 종합면접이었다. 근데 30분? 정도 걸렸는데 이게 정말 역대급이다. 별걸 다 묻는다. 나 혼자 갈등 경험 정리하고 그랬는데.. 4일 내내 달달 외우던거 쓸모 없었다.

내 자기소개서는 대부분 기술기반 경험이었고, 그렇기에 1차 면접보다 더 실무에 가까운 질문을 받았다.

그 외 지원 동기나 인턴 경험 관련 질문도 받았는데, 질문 자체가 명확하지 않고 두루뭉실하기 때문에 본인이 조리있게 설명해야 한다.

창의 수학 문제도 냈다 ㅋㅋ ㅠ.. (왜 내신건지 잘 모르겠다.) 순간 멍해져서 "생각 할 시간을 주세요." 그러고 5분~10분 정도 고민했다. 맞췄으니 다행이지 말도 못했으면 답도 없다.

추가로, 본인이 생각하는 엔지니어링에 대한 가치관을 잘 정립해가면 도움이 많이 된다.

더보기

내 경우는 최소 비용으로 원하는 시스템을 구축하는 것이었고 이를 위해 알고리즘뿐만 아니라 CS기초지식을 바탕으로 여러 기술 스택을 비교하고 더 나은 방법을 선택하기 위해 많은 경험이 필요하다고 답했었다. 그러기 위해 네이버라는 회사는 큰 데이터셋을 가지기 때문에 도움이 될 거라고 말했다. 회사를 위한다기 보다 내 커리어 패스에 관한 이야기였다.

  • 1차 면접에서 문제를 못풀면 그걸 다시 물어본다고 한다. 아마 내 경우는 다 풀어서 면접 시간이 짧았던 거 같다.
  • 자기소개서에 협업 관련 경험을 적어놓았다면, 높은 확률로 인성 질문을 받을 것이다. 예를 들어, 갈등 경험
  • 인성 관련 질문 왜 안하냐고 물었더니, 면접관이 보고자 하는 것은 그 사람의 인성이 아니라 얼마나 끈기 있게 해왔는지를 본다고 했다.
  • 압박 면접이었다. 이건 지나가는 개를 붙잡고 물어봐도 맞다고 할거다.

후기

대기업, 중소기업, 난다긴다하는 스타트업 등 면접 많이 봤었는데 생각보다 네이버가 제일 어려웠다. IT대기업이다 보니 신입 공채에서 실무 질문은 안할 줄 알았다. 근데 기초/실무/수학/CS/커리어 패스/가치관 등등 별걸 다 물었다.

마냥 열심히 하기 보다 어떤 목적과 목표를 가지고 공부를 해왔는지 보고 싶어 하는 듯하다.

  • 그러나 모든 면접이 그렇듯 대답 못하는 것이 존재할 수 밖에 없고, 대답 못하더라도 적절한 긴장감으로, 느리더라도 자신의 생각을 명확하게 이야기해야 될 것이다. (그렇게 못해서 떨어진게 한 두번이 아니다....ㅜ)
  • 네이버 면접은 후기를 찾기가 어려워서 기록용으로 남겼다. 다만, 문제는 발설할 수 없기 때문에 그 때의 느낌만 적어보았다.

'Review' 카테고리의 다른 글

종만북 후기 (알고리즘 문제해결전략)  (6) 2020.11.28

본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


TCP/IP 네트워크 개요

이더넷 프레임

  • 월드 와이드 웹(이하 WWW)은 거대한 IP 네트워크로, 연결된 기계들은 할당된 고유한 IP 주소(32비트 숫자)로 식별됨
    • IP 주소는 네트워크 주소와 호스트 주소로 나누어 구분할 수 있음
    • 호스트 주소는 서브넷(subnetwork, subnet)과 호스트 주소로 더 자세히 나눌 수 있으며, 네트워크를 사용하는 기관은 자신의 네트워크를 몇 구획으로 나눌 수 있음
    • 네트워크 관리자는 IP 주소를 할당할 때, IP 서브넷을 사용하여 네트워크 관리 부담을 분산시킴
    • IP 서브넷 관리자는 자신의 IP 서브넷 내에서 자유롭게 IP 주소를 할당

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 주소는 숫자가 많아 외우기 어려우며, 문자열로 된 네트워크 이름을 부여해 기계들은 통신 할 수 있음
  • 네트워크 이름을 IP 주소로 변환해주는 작업은, /etc/hosts 파일에 정적으로 명시하거나 분산 네임 서버(Distributed Name Server, DNS)에 변환 요청을 보내서 처리
    • 로컬 호스트는 하나 이상의 DNS 서버의 IP 주소를 알고 있어야 하며, 이 주소들을 /etc/resolv.conf 에 기록
    • 웹 페이지를 읽을 때처럼 다른 호스트(서버)에 접속할 때, 데이터를 교환하기 위해 접속할 대상의 IP 주소를 사용
  • 데이터들은 IP 패킷(packet)에 담겨 전달되며, 각 패킷마다 출발지 호스트와 목적지 호스트의 IP 주소, 체크섬(checksum) 및 IP 헤더가 추가됨
    • 체크섬은 IP 패킷에 있는 데이터를 가지고 계산한 것으로, IP 패킷 수신자(목적지 호스트)는 전화선의 잡음 등에 의해 전달 중에 패킷이 손상되었는지 판단
    • 데이터는 다루기 쉽게 작은 패킷들로 쪼개져서 보내질 수 있고, 목적지 호스트는 패킷들을 다시 조합하여 프로세스에게 전달

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 같은 IP 서브넷에 있는 호스트끼리는 IP 패킷을 직접 보낼 수 있지만, 그렇지 않으면 게이트웨이(gateway) 또는 라우터(router)라고 하는 특별한 호스트에 IP 패킷을 전송 (게이트웨이나 라우터는 한 IP 서브넷에서 다른 IP 서브넷으로 패킷을 전달하는 역할)
  • 각 호스트들은 정확한 목적지로 IP 패킷을 전달하기 위해 라우팅 테이블(routing table)을 작성해서 다음 도착지로 패킷을 전달
    • 라우팅 테이블에는 모든 IP 목적지에 대해 다음 도착지를 결정하는데 필요한 정보를 포함
    • 이 테이블은 동적으로 변경되는데, 네트워크를 사용하거나 네트워크 구성도가 변경되면 시간이 지나면서 바뀜
  • TCP 프로토콜은 신뢰할 수 있는 일대일 프로토콜로, 데이터를 주고 받기 위해 IP 프로토콜을 사용하며, IP 프로토콜은 TCP 외에 다른 프로토콜이 데이터를 보낼 때 사용하는 전송 계층
    • IP 패킷에 헤더가 붙는 것처럼 TCP 패킷에도 헤더가 추가됨
    • TCP 프로토콜로 통신하는 두 프로세스는 통신 과정에서 많은 서브넷, 게이트웨이, 라우터가 있더라도 하나의 가상 접속으로 연결됨 (연결 지향 프로토콜)
    • TCP 프로토콜은 데이터의 손실이나 중복이 없다는 것을 보장 (신뢰할 수 있는 프로토콜)
    • TCP 프로토콜이 IP 프로토콜을 통해 TCP 패킷을 전송할 때, TCP 패킷에 헤더까지 포함된 것이 IP 패킷의 데이터
    • 서로 통신하고 있는 호스트의 IP 계층은 IP 패킷을 주고 받는 역할 (받을 때는 헤더를 제거한 데이터를 TCP 계층으로 보냄)
    • UDP 프로토콜은 TCP 프로토콜과는 달리 신뢰할 수 없는 프로토콜로, IP 계층을 사용하여 데이터그램(datagram) 서비스를 제공하는데 패킷의 순서와 도착을 보장하지 않음
    • IP 프로토콜은 IP 패킷에 담긴 데이터를 전달할 상위 프로토콜을 결정하기 위해 모든 IP 패킷 헤더에 프로토콜 식별자를 지정하는 바이트가 존재 (e.g., TCP라면 IP 패킷 헤더에 데이터가 TCP 패킷인 것을 기록)
  • 프로그램이 TCP/IP 계층으로 통신할 때, 프로세스는 상대 IP 주소 외에 포트(port)도 명시해야 함
    • 포트 번호는 프로세스마다 유일한 것으로, 표준 네트워크 프로세스는 표준 포트 번호를 사용 (e.g., 웹서버의 경우 80번)
    • 등록된 포트 번호는 /etc/services 파일에서 확인 가능
  • 프로토콜의 계층 구조는 TCP/UDP 및 IP로 구분하지 않고, IP 프로토콜 자체도 패킷을 전달하는데 여러 장치를 사용하기 때문에 각 장치에서 각자의 프로토콜 헤더를 추가하기도 함
    • 예를 들어, 이더넷 네트워크에서 많은 호스트가 실제로 하나의 케이블에 동시에 접속할 수 있으므로, 전송되는 모든 이더넷 프레임은 연결된 모든 호스트에게 보여짐 (단, 모든 이더넷 장치는 고유한 주소를 가짐)
    • 호스트는 자기 주소로 전달되는 모든 이더넷 프레임을 받아들이지만, 같은 네트워크에 연결된 다른 호스트들은 이들을 무시
    • 이더넷 주소는 6바이트 길이로 (흔히 MAC 주소로 알려진), "08-00-2B-00-49-A4"와 같은 형식
    • 어떤 이더넷 주소는 멀티캐스트(multicast) 목적으로 예약되어 있어, 이 주소로 보내지는 이더넷 프레임들은 같은 네트워크 안에 있는 모든 호스트가 수신함
    • 이더넷 프레임은 데이터로 수많은 프로토콜들을 전송할 수 있기 때문에, 헤더에 프로토콜 식별자가 존재하며, 이더넷 계층은 정확하게 IP 패킷을 IP 계층에 전달 가능
  • 이더넷 같은 다중 접속 프로토콜을 통해 IP 패킷을 보내려면, IP 계층은 IP 호스트의 이더넷 주소를 탐색 (IP 주소는 개념적 주소이며, 이더넷 장치는 고유한 물리적 주소를 가짐)
    • IP 주소는 네트워크 관리자에 의해 지정되고 변경될 수 있으나, 네트워크 하드웨어는 각자의 물리적 주소 또는 멀티캐스트 주소에만 반응함 (즉, 이더넷 주소는 변경할 수 없음)
  • IP 주소를 이더넷 주소 같은 실제 하드웨어 주소로 변환하는 작업은, 주소 변환 프로토콜(ARP)을 사용해서 처리
    • 변환하고자 하는 IP 주소가 담긴 ARP 요청 패킷을 멀티캐스트 주소로 보내 모든 연결된 호스트에 전달
    • 그 IP 주소를 가지고 있는 호스트는 자신의 하드웨어 주소를 ARP 응답 패킷에 담아서 응답
    • ARP 요청이 불가능한 장치들은 별도로 표시하여 ARP를 시도하지 않음
  • 하드웨어 주소를 IP 주소로 변환하는 작업은, RARP를 사용해서 처리. 보통 이 기능은 게이트웨이가 사용하며, 원격 네트워크에 있는 IP 주소를 대신해서 게이트웨이가 ARP 요청에 응답

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

리눅스의 TCP/IP 네트워크 계층

리눅스 네트워크 계층

  • 리눅스는 인터넷 프로토콜 주소 패밀리를 일련의 연관된 소프트웨어 계층으로 구현
  • BSD Sockets 계층 일반적인 소켓 관리 소프트웨어가 BSD 소켓만 처리
  • INET Sockets 계층 소켓 관리 소프트웨어를 지원하는데, IP 기반 프로토콜인 TCP/UDP의 통신 종점을 관리
  • TCP(Transmission Control Protocol) 연결 지향의 신뢰할 수 있는 일대일 프로토콜 (TCP 패킷들에 번호를 매겨 종점 호스트는 데이터를 수신할 때 패킷 순서 및 손실을 확인)

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • UDP(User Datagram Protocol) 비연결지향 방식의 프로토콜 (패킷이 전송 시 제대로 도착했는지 확인 불가)
  • IP 계층 인터넷 프로토콜을 구현한 계층으로, 전송하는 데이터 앞에 IP 헤더를 붙이고, 들어오는 IP 패킷의 헤더를 제거하여 TCP나 UDP로 전달
  • IP 계층 아래의 PPP 또는 이더넷 같은 네트워크 장치들이 리눅스의 모든 네트워킹을 지원하며, 네트워크 장치는 항상 물리 장치를 가리키는 것이 아니라 루프백 장치 같은 몇몇 순수 소프트웨어로 작성된 것도 존재
    • 네트워크 장치는 다른 장치들과 달리 관련 소프트웨어가 장치를 찾아 초기화해야 장치 파일로 보여짐
    • 해당 이더넷 디바이스 드라이버를 추가하여 커널을 빌드해야만 /dev/eth0 를 볼 수 있음

https://www.educative.io/edpresso/tcp-vs-udp

BSD 소켓 인터페이스

  • 다양한 형태의 네트워킹 외에 프로세스간 통신도 지원하는 일반적인 인터페이스
  • 통신하고 있는 두 프로세스는 연결 시, 데이터를 주고 받기 위한 소켓을 가지며, 파이프와 달리 소켓은 저장가능한 데이터 용량이 제한되지 않음
  • 주소 패밀리(Address Family) 소켓의 클래스, 각 클래스별로 통신에 사용하는 주소 표현법을 가짐
    • UNIX 유닉스 도메인 소켓
    • INET TCP/IP 프로토콜을 이용한 통신을 지원하는 인터넷 주소 패밀리
    • AX25 아마추어 라디오 X.25
    • IPX 노벨의 IPX 프로토콜
    • APPLETALK 애플사의 Appletalk DDP 프로토콜
    • X25 X.25 프로토콜
  • 소켓에는 접속을 지원하는 서비스의 종류를 나타내는 타입이 존재 (모든 주소 패밀리가 모든 형태의 서비스를 지원하지는 않음)
    • Stream 이 소켓은 데이터가 전송 중 분실, 오염, 또는 중복되지 않는다는 것을 보장하는 신뢰성 있는 양방향 순차 데이터 스트림
    • Datagram 이 소켓은 양방향 데이터 전송을 제공하나, 메시지 도착 유무, 순서 보장, 중복 제거, 오염 유무 등을 보장하지 않음
    • Raw 프로세스가 하부 프로토콜에 직접 접근 가능. 이더넷 장치에 이 소켓을 열어 가공되지 않은 IP 데이터 흐름을 볼 수 있음
    • Reliable Delivered Messages 데이터그램 소켓과 유사하지만, 데이터가 목적지에 도착한다는 것을 보장
    • Sequenced Packets 스트림 소켓과 유사하며, 데이터 패킷 크기가 고정됨
    • Packet 표준 BSD 소켓 타입이 아니며, 장치 수준에서 프로세스가 직접 패킷에 접근할 수 있는 확장 패킷 유형
  • 소켓을 사용하여 통신하는 프로세스는 클라이언트 서버 모델을 따르며, 서버는 서비스를 제공하고, 클라이언트는 이 서비스를 이용
    • 서버는 먼저 소켓을 생성하여 이름(소켓 주소 패밀리에 따라 다르며 대개 로컬 주소)을 bind한 후, 소켓의 이름 또는 주소를 sockaddr 구조체에 명시 (INET 소켓은 그것에 바인드된 IP 포트 번호를 가짐)
    • 서버는 바인드된 주소를 가리키는 연결 요청이 들어오는 listen
    • 클라이언트는 소켓을 생성하여 서버의 주소를 명시하여 서버측 소켓에 대해 연결 요청 (INET 소켓의 경우 포트 번호를 포함)
    • 연결 요청은 다양한 프로토콜 계층을 통해 전달되어 서버의 소켓에 도달하고, 서버는 요청을 받고 수락(accept) 또는 거절(reject) 할 수 있음
    • 요청을 받아들이면, 새로운 소켓을 생성 (listen 소켓은 연결 요청을 받아들이는데만 사용 가능)
    • 연결이 성립되면, 서버와 클라이언트는 자유롭게 데이터를 주고 받음
    • 연결이 필요없는 경우, 소켓을 종료(shutdown)하며, 이때 전송 중인 데이터 처리에 유의해야 함

https://rastating.github.io/using-socket-reuse-to-exploit-vulnserver/

  • 커널 초기화 과정에서 커널에 구현된 주소 패밀리는 BSD 소켓 인터페이스와 함께 커널에 자신을 등록
  • 프로세스가 BSD 소켓을 만들어 사용할 때, BSD 소켓과 지원하는 주소 패밀리 사이의 연관관계는 connection 자료구조와 주소 패밀리 고유의 지원 루틴 테이블을 통해 생성 (프로그램이 새 소켓을 생성할 때, 주소 패밀리 고유의 소켓 생성 루틴이 존재)
  • 커널을 설정할 때 주소 패밀리와 프로토콜을 protocols 배열에 추가. 이 배열에는 각 주소 패밀리 또는 프로토콜의 이름(e.g., INET)과 초기화 루틴이 포함되어 있음
  • 부팅 시 소켓 인터페이스를 초기화할 때, 각 프로토콜의 초기화 루틴이 호출되고, 소켓 주소 패밀리마다 일련의 프로토콜 연산 루틴을 등록 (이것은 루틴들의 집합이며, 각 루틴은 해당 주소 패밀리의 고유한 특정 연산을 수행)
  • proto_ops 구조체는 주소 패밀리 타입과 특정 주소 패밀리에 고유한 소켓 연산 루틴에 대한 포인터들의 집합으로 구성됨
  • pops 배열은 인터넷 주소 패밀리 같은 (AF_INET은 2) 주소 패밀리 식별자로 인덱싱

https://slidetodoc.com/socket-layer-coms-w-6998-spring-2010-erich/

INET 소켓 계층

  • TCP/IP 프로토콜을 포함하는 인터넷 주소 패밀리를 지원, 한 프로토콜이 다른 프로토콜의 서비스를 이용하며, 리눅스 TCP/IP 코드와 자료구조는 프로토콜들의 계층 구조를 반영
  • BSD 소켓 계층과의 인터페이스는 네트워크 초기화 중에 BSD 소켓 계층에 등록한 인터넷 주소 패밀리 소켓 함수들의 집합
    • BSD 소켓 계층에서 pops 배열에 등록된 다른 주소 패밀리와 함께 보관됨
    • BSD 소켓 계층은 등록된 INET proto_ops 구조체로부터 INET 계층의 소켓 지원 루틴을 호출하여 작업
    • 주소 패밀리에 INET을 주고 BSD 소켓 생성을 요구하면, 이는 하위 INET 소켓 생성 함수를 호출
  • BSD 소켓 계층은 각 함수를 호출할 때마다 INET 계층에 있는 BSD 소켓을 나타내는 socket 구조체를 전달하고, socket 구조체에 있는 data 포인터는 sock 구조체를 참조하는데, 이 구조체는 INET 소켓 계층에서 사용됨
  • sock 구조체의 프로토콜 함수 포인터는 생성 시 설정되는 것으로, 요구한 프로토콜에 따라 다름. 만약 TCP를 요구했다면, 프로토콜 함수 포인터는 TCP 연결을 위해 필요한 TCP 프로토콜 함수 집합을 참조

  • BSD 소켓 생성
    • 새 소켓을 만드는 시스템 콜에는 주소 패밀리 식별자, 소켓 타입, 프로토콜을 인자로 넘겨야 하며, 요구한 주소 패밀리를 사용하여 pops 배열에서 일치하는 주소 패밀리를 탐색
      • 어떤 주소 패밀리는 커널 모듈에 의해 생성되었기 때문에, kerneld 데몬이 이 모듈을 이용해서 주소 패밀리를 탐색
    • BSD 소켓을 나타내기 위해 새 socket 구조체를 할당. 이 구조체는 VFS inode 구조체의 일부이며 실제로 VFS inode를 할당하는 것과 같음 (이는 소켓이 일반 파일과 동일하게 작동하게 하며, 파일 함수들을 이용해 소켓을 열고, 닫고, 읽고, 쓸 수 있음)
    • socket 구조체는 주소 패밀리에 따라 특수한 소켓 루틴들에 대한 포인터를 포함하며, pops 배열에서 얻을 수 있는 proto_ops 구조체에 이 포인터들이 설정됨 (타입은 요구한 소켓 타입으로 설정: SOCK_STREAM 또는 SOCK_DGRAM 등)
    • proto_ops 구조체에 있는 주소를 호출하면, 주소 패밀리에 따라 다른 생성 루틴이 실행
    • 현재 프로세스의 fd 배열에 텅빈 파일 기술자가 할당되고 이는 초기화된 file 구조체를 참조하며 이 구조체의 파일 함수 포인터가 BSD 소켓 파일 함수들을 가리키도록 설정
    • 이후 소켓 파일 연산들은 BSD 소켓 인터페이스로 전달되며, 인터페이스는 차례로 주소 패밀리의 함수들을 호출함으로써 각 주소 패밀리로 작업을 전달
  •  bind: 주소와 INET BSD 소켓 바인딩
    • 각 서버는 INET BSD 소켓(접속 요청을 받는 listen 용도)을 만들어 서버의 주소와 바인드
      • 주소와 바인드된 소켓은 다른 통신을 위해서는 사용할 수 없음 (socket 상태는 TCP_CLOSE)
    • 바인드 작업은 대부분 INET 소켓 계층이 아래 계층인 TCP/UDP 프로토콜 계층으로부터 어느 정도 지원을 받아 처리
    • INET 주소 패밀리를 지원하며, 네트워크 장치에 할당된 IP 주소가 바인드된 주소
      • IP 주소는 모두 1 또는 0인 IP 브로드캐스트(broadcast) 주소일 수 있으며, 이는 모든 호스트에게 보내라는 의미
      • 단, 슈퍼유저 권한의 프로세스만이 아무 IP 주소에나 바인드 할 수 있음
      • 바인드된 IP 주소는 recv_addr 구조체에 있는 sock 구조체와  saddr 항목에 저장됨
    • 포트 번호는 옵션이며, 지정하지 않으면 이를 지원하는 네트워크에게 빈 포트 번호를 요청
      • 1024보다 작은 포트 번호는 슈퍼유저 권한이 없는 프로세스는 사용할 수 없음 (well-known ports)
    • 네트워크 장치는 패킷을 받으면, 이를 올바른 INET 과 BSD 소켓으로 전달하여 처리
    • TCP/UDP는 들어온 IP 메시지에 있는 주소를 조회하여 올바른 socket/sock 쌍으로 전달하기 위한 해시 테이블을 관리
      • TCP는 연결 지향 프로토콜로, UDP 패킷을 처리할 때보다 TCP 패킷을 처리할 때 더 많은 정보가 사용됨
      • UDP는 할당된 UDP 포트의 해시 테이블인 udp_hash 자료구조를 관리
      • 이는 sock 구조체의 포인터 배열로, 포트 번호에 기반하여 해시 값을 계산
    • TCP는 바인드 작업 동안에 바인드하는 sock 구조체를 해시 테이블에 추가하지 않고, 단지 요구한 포트 번호가 현재 사용중인지만 검사 (sock 구조체는 listen 작업 중에 TCP 해시 테이블에 추가됨)
  • connect: INET BSD 소켓으로 연결
    • 연결 요청을 받는 용도(listen)로 사용되지 않으면, 연결 요청을 하는 용도(connect)로 사용 가능
    • UDP는 비연결지향이므로 이런 작업이 필요 없으며, TCP만 두 프로세스 사이에 가상 연결을 생성할 때 필요함
      • UDP도 접속 BSD 소켓 함수를 지원하지만, UDP INET BSD 소켓에서의 접속 작업은 단순히 원격지의 IP 주소 및 포트 번호를 설정하는 것
      • 추가적으로 라우팅 테이블 엔트리에 대한 캐시를 설정하여, 이 BSD 소켓으로 보낸 UDP 패킷이 다시 라우팅 DB를 검사하지 않도록 함 (경로가 틀리지 않은 경우)
      • 캐시된 라우팅 정보는 INET sock 구조체에서 ip_route_cache 에 의해 참조됨
      • UDP는 sock 상태를 TCP_ESTABLISHED 로 변경
    • TCP는 접속 정보를 가진 메시지를 하나 생성해 목적지로 전달하며, 이 메시지는 시작 메시지 순서 번호와 시작하는 호스트에서 처리할 수 있는 메시지 최대 크기, 송수신 윈도우 크기(아직 보내지 않은 메시지들 중 보관 가능한 메시지의 수) 등을 포함
      • 최대 메시지 크기(MTU)는 요청을 시작한 쪽에서 사용하고 있는 네트워크 장치에 따라 바뀜
      • 받는 쪽의 네트워크 장치가 이보다 작은 최대 메시지 크기를 지원하면 접속중 최소 MTU 값을 사용
    • TCP에서는 모든 메시지에 번호가 붙으며, 초기 순서 번호는 첫번째 메시지 번호와 같고, 악의적인 프로토콜 공격을 피하기 위해 허용 범위 내에서 임의값으로 번호를 지정
    • TCP에서 메시지를 수신하는 호스트는 모든 메시지에 대해 성공적으로 도착하였다는 응답을 송신지로 전달. 만약 이 응답이 없으면 송신측에서 같은 메시지를 재전송
    • TCP 접속 요청을 원하는 프로세스는 요청이 accept/reject 중 하나라는 응답을 받을 때까지 블락되며, 타이머를 걸어 타임아웃시 프로세스가 실행되도록 할 수 있음
    • TCP는 메시지가 들어올 때까지 대기하며, tcp_listening_hash 를 추가하여 들어온 메시지가 sock 구조체로 전달되게 함
    • 클라이언트가 연결을 요청하고, 서버가 요청을 수락하는 과정에서 TCP는 총 3개의 패킷을 주고 받음
      • 이를 3-Way Handshaking 이라고 함
    • 클라이언트가 연결 종료를 요청하고, 서버가 요청을 받고 소켓을 종료하는 과정에서 TCP는 총 4개의 패킷을 주고 받음
      • 이를 4-Way Handshaking 이라고 함

3-way handshaking
https://asfirstalways.tistory.com/356

  • listen: INET BSD 소켓에서 접속 요청 대기
    • 소켓에 주소를 바인딩 했으면, 바인드한 주소를 지정하여 들어오는 접속 요청을 대기하는데, listen 함수는 소켓의 상태를 TCP_LISTEN 으로 바꾸고 들어오는 접속을 허가하기 위해 필요한 특수 작업을 처리
    • 먼저 주소를 바인드하지 않고 접속을 기다릴 수 있으며, 여기서 INET 소켓 계층은 사용하지 않는 포트 번호를 찾아 소켓에 지정
    • UDP는 소켓 상태만 변경하는 반면, TCP는 sock 구조체를 두 개의 해시 테이블(tcp_bound_hash, tcp_listening_hash)에 추가 (두 해시 테이블 모두 IP 포트 번호에 기반한 해시 함수를 통해 인덱싱)
    • listen 소켓은 TCP 접속 요청을 받으면 이를 나타내는 sock 구조체를 생성하고, 접속 요청을 포함한 sk_buff 구조체를 복사하여 sock 구조체의 receive_queue 뒤에 이를 추가
    • 복사한 sk_buff 는 새로 만든 sock 구조체에 대한 포인터를 저장

https://myaut.github.io/dtrace-stap-book/kernel/net.html

  • accept: 접속 요청 허가
    • INET 소켓 접속을 허락하는 것은 TCP 프로토콜에만 적용 (UDP는 해당 없음)
    • listen 소켓 외에 accept/reject 을 위한 소켓에서 socket 구조체를 복사하여 새 socket 구조체를 생성
    • 지원하는 프로토콜 계층(TCP)에 허가하라는 명령을 전달
    • 블럭킹 모드가 아닌 경우, 들어오는 접속이 없으면, 위 작업은 실패하며 새로 만들어진 socket 구조체는 제거됨
    • 블럭킹 모드이면, 접속을 허가하는 프로세스는 대기 큐에 추가되고, 요청을 받을 때까지 중단됨
    • 클라이언트는 서버로부터 ACK 받으면 다시 ACK(접속을 허용 해달라는 메시지)를 보내는데, 이때 sk_buff 구조체는 무시되고, sock 구조체는 이전에 새로 만든 socket 구조체와 연결된 INET 소켓 계층으로 반환됨
    • 새로 생성된 소켓의 파일 기술자(fd)를 프로세스에게 돌려주고, 프로세스는 새 BSD 소켓을 가지고 작업할 때마다 fd를 사용

TCP 연결 과정
https://myaut.github.io/dtrace-stap-book/kernel/net.html

IP 계층

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 소켓 버퍼(Socket Buffer)
    • 여러 네트워크 프로토콜 계층을 가지면, 각 계층에서 다른 계층의 서비스를 사용하게 되는데, 전송할 때는 데이터에 헤더와 테일을 붙이고, 받을 때는 데이터에서 헤더와 테일을 제거해야 하는 오버헤드가 존재
    • 패킷에서 프로토콜 헤더와 테일을 탐색해야 하므로, 프로토콜 사이에 데이터 버퍼를 전달하는 것을 어렵게 함 (단순히 버퍼를 복사하는 것은 매우 비효율적)
    • 리눅스는 프로토콜 계층 사이에서 네트워크 디바이스 드라이버 간에 데이터를 주고 받기 위해, sk_buff 구조체의 리스트 포인터를 이용하는 소켓 버퍼를 사용
    • 소켓 버퍼는 포인터를 이용해 각 프로토콜 계층이 표준 함수로 프로그램 데이터를 다룰 수 있게 함
    • 각 sk_buff 구조체는 각자의 데이터 블럭을 가지며, 데이터를 다루고 관리하기 위해 4개의 데이터 포인터를 포함
      • head 메모리에서 데이터의 시작 위치를 참조 (블럭 할당 시 고정)
      • data 현재 프로토콜 데이터의 시작 위치를 참조 (현재 sk_buff 구조체를 소유하고 있는 프로토콜 계층에 따라 바뀜)
      • tail 현재 프로토콜 데이터의 끝 위치를 참조 (현재 sk_buff 구조체를 소유하고 있는 프로토콜 계층에 따라 바뀜)
      • end 메모리에서 데이터의 끝 위치를 참조 (블럭 할당 시 고정)
    • sk_buff 구조체를 다루는 코드는 데이터에 헤더와 테일을 붙이고 제거하는 표준 함수를 제공
      • len 현재 프로토콜 패킷의 길이 (data ~ tail)
      • truesize 데이터 버퍼의 전체 크기 (head ~ end)
      • push() data 포인터를 데이터 영역의 시작 쪽으로 이동시키고, len 값을 증가시킴. 전송할 패킷의 시작 부분에 데이터나 프로토콜 헤더를 추가하는데 사용
      • pull() data 포인터를 데이터 영역의 끝 쪽으로 이동시키고, len 값을 감소시킴. 수신한 패킷의 시작 부분에서 데이터나 프로토콜 헤더를 제거하는데 사용
      • put() tail 포인터를 데이터 영역의 끝 쪽으로 이동시키고, len 값을 증가시킴. 전송할 패킷의 끝에 데이터나 프로토콜 정보를 추가하는데 사용
      • trim() tail 포인터를 데이터 영역의 시작 쪽으로 이동시키고, len 값을 감소시킴. 수신한 패킷의 끝 부분에서 데이터나 프로토콜 정보를 제거하는데 사용

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 디바이스 드라이버 초기화의 결과는 dev_base 리스트에서 서로 연결되어 있는 일련의 device 구조체들로, 각 구조체는 장치를 기술하고 네트워크 프로토콜 계층에서 드라이버가 작업할 때 호출하는 콜백 함수 집합을 제공
    • 이들 함수들은 대부분 데이터 전송 및 네트워크 장치 주소와 관련된 것

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 패킷 수신
    • 네트워크 장치가 패킷을 수신하면 이들은 sk_buff 구조체로 변환되어, backlog 큐에 sk_buff 구조체가 추가됨
      • 만약 backlog 큐가 너무 커지면, 패킷을 무시
    • 이후 실행 준비가 되었음을 네트워크 하반부(bottom half)에 표시하고, 스케줄러는 하반부 핸들러를 실행하는데, 이 핸들러는 backlog 큐를 처리하기 전에 수신한 패킷을 전달할 프로토콜 계층을 결정하며, 전송 대기 중인 패킷들을 처리
    • 네트워크 계층은 초기화될 때, 각 프로토콜은 packet_type 구조체를 ptype_all 리스트나 ptype_base 해시 테이블에 추가하여 자신을 커널에 등록
      • packet_type 구조체는 프로토콜 타입, 네트워크 장치에 대한 포인터, 프로토콜의 수신 데이터 처리 루틴 및 리스트나 해시 테이블에 있는 다음 packet_type 구조체에 대한 포인터를 포함
      • ptype_base 해시 테이블은 프로토콜 식별자로 인덱싱되어, 수신한 패킷을 받을 프로토콜을 결정하기 위해 사용됨
    • 네트워크 하반부 핸들러는 들어오는 sk_buff 구조체의 프로토콜 타입과 ptype_base 해시 테이블에 있는 하나 이상의 packet_type 엔트리와 비교 (반드시 프로토콜은 하나 이상의 엔트리와 매칭됨)

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 패킷 송신
    • 프로그램이 전송할 데이터를 생성하면, 데이터를 포함한 sk_buff 구조체가 만들어지고, 각 프로토콜 계층을 위에서 아래로 통과하면서 계층별 헤더가 추가됨
    • sk_buff 구조체는 전송할 네트워크 장치로 전달되며, IP 같은 프로토콜은 사용할 장치를 결정
    • 패킷은 루프백 장치를 통해 PPP 모뎀 연결의 끝에 있는 게이트웨이 또는 로컬 호스트 둘 중 하나로 전달됨
    • IP 패킷을 전송할 때는 도달할 IP 주소로 가는 루트(route)를 결정하기 위해 라우팅 테이블(routing table)이 사용되며, 각 IP 목적지는 자신의 라우팅 테이블로부터 다음 도착지를 알아내 루트를 기술하는 rtable 구조체를 반환
      • 이 구조체는 사용할 출발지 IP 주소, 네트워크 device 구조체의 주소, 간혹 미리 만들어진 하드웨어 헤더를 포함
      • 하드웨어 헤더는 네트워크 장치마다 다르며, 출발지와 도착지의 하드웨어 주소와 매개체 별로 다른 정보를 포함
      • 예를 들어, 이더넷 장치이면 출발지/도착지 주소는 물리적 주소(이던세 주소)
    • 하드웨어 헤더는 루트와 함께 캐시되는데, 이 헤더가 해당 루트를 통하여 전송하는 모든 IP 패킷에 추가되는 과정을 줄임
    • 하드웨어 헤더는 ARP 프로토콜로 해결(resolve)되어야 하는 물리 주소가 필요할 수 있는데, 패킷은 주소가 해결될 때까지 블락됨 (한 번 ARP를 요청하면 캐싱하므로 재요청이 필요 없음)
  • 데이터 조각내기(Data Fragmentation)
    • 모든 네트워크 장치는 최대 패킷 크기(MTU)를 가지기 때문에 이보다 큰 크기의 데이터를 보내거나 받기 위해 IP 프로토콜은 데이터를 처리 가능한 크기로 나눔
    • IP 패킷을 보내기 위해, IP 라우팅 테이블에서 네트워크 장치를 찾고, 패킷 크기가 MTU 보다 크면 크기에 맞춰 데이터를 조각내서 각 sk_buff 구조체를 전달 (패킷을 쪼개는 도중에 IP가 sk_buff 구조체를 할당받지 못한다면 전송은 실패)
    • IP 패킷 헤더는 플래그와 이 조각의 오프셋을 가리키는 항목을 포함하며, 마지막 패킷은 마지막 IP 조각이라고 표시
    •  IP 패킷을 받기 위해, IP 패킷 조각이 임의의 순서로 도착하는 점을 고려하여 모두 받은 뒤 재조립
      • 수신할 때마다 패킷 조각인지 검사하고, 처음 받은 경우 새 ipq 구조체를 생성
      • ipq 구조체는 재조립을 기다리는 IP 패킷 조각 리스트인 ipqueue 에 추가됨
      • IP 패킷 조각을 받을 때마다, 맞는 ipq 구조체를 찾기 위해 이 조각을 나타낼 ipfrag 구조체를 생성함 (ipq 구조체에서 fragments 변수에 저장됨)
      • 각 ipq 구조체는 조각난 IP 수신 프레임을 출발지와 도착지 IP 주소와 함께 유일하게 기술하며, 위 프로토콜 계층의 식별자와 해당 IP 프레임의 식별자를 가짐
      • 모든 조각이 도착하면, 데이터들은 하나의 ipq 구조체에서 리스트로 보관되므로, 이들을 sk_buff 구조체 하나로 병합
      • 병합된 sk_buff 구조체를 다음 프로토콜 계층으로 전달
    • 각 ipq 구조체는 조각이 도착할 때마다 타이머를 시작하는데, 타임아웃되면 ipq 구조체와 이것의 ipfrag 구조체들은 메모리에서 소멸되어 메시지는 전송 중 사라진 것으로 간주 (이들의 재전송은 상위 프로토콜의 책임)

데이터 조각화
https://www.slideshare.net/hugolu/the-linux-networking-architecture

주소 결정 프로토콜(Address Resolution Protocol, ARP)

  • IP 주소에서 이더넷 주소 같은 하드웨어 주소로의 변환을 제공하는 프로토콜
  • IP는 디바이스 드라이버에게 데이터를 sk_buff 구조체로 전달하기 전에 ARP를 호출해서 하드웨어 주소를 설정
  • 이 장치가 하드웨어 헤더를 필요로 한다면, 패킷용으로 하드웨어 헤더를 다시 만들어야 하는지 알기 위해 여러 검사를 수행
  • 리눅스는 하드웨어 헤더를 자주 만들지 않도록 캐시를 사용하며, 다시 만들어야 한다면 장치 고유의 하드웨어 헤더 재생성 루틴을 호출
  • 모든 이더넷 장치는 동일한 헤더 재생성 루틴을 호출하며, 이 루틴은 목적지 IP 주소를 물리 주소로 바꾸기 위한 ARP 서비스를 사용
  • ARP 요청과 응답 두 가지 메시지 형태가 존재: 요청은 변환할 IP 주소를 가지며, 응답은 하드웨어 주소(변환된 IP 주소)를 가짐
  • ARP 요청은 네트워크에 연결된 모든 호스트로 전달(브로드캐스트)되므로, 이더넷에 연결된 모든 호스트들이 ARP 요청을 수신하게 되는데 해당 IP 주소의 호스트만 응답

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

  • ARP 계층은 각 IP 주소에서 하드웨어 주소로의 변환을 나타내는 arp_table 자료구조를 가지며, 각 엔트리는 IP 주소가 변환될 필요가 있을 때 만들어지고 시간이 지나면서 제거됨
    • last used arp_table 엔트리가 마지막으로 사용된 시간
    • last updated arp_table 엔트리가 마지막으로 수정된 시간
    • flags arp_table 엔트리가 완료되었는지 같은 상태를 나타내는 플래그
    • ip address 엔트리가 나타내는 IP 주소
    • hardware address 변환된 하드웨어 주소
    • hardware header 캐시된 하드웨어 헤더에 대한 포인터
    • timer 응답하지 않는 요구를 타임아웃시키기 위한 timer_list 리스트의 엔트리
    • retries ARP 요청을 재시도한 횟수
    • sk_buff queue 이 IP 주소를 해결(resolve)하길 기다리는 sk_buff 구조체들의 리스트
  • arp_tables 배열은 arp_table의 엔트리들에 대한 캐시로, 각 엔트리는 IP 주소의 끝 두 바이트를 가져와 인덱싱되며, 원하는 엔트리를 찾기 위해 해시 테이블에서 배열을 인덱싱하여 얻은 리스트를 순회
  • 미리 만들어진 하드웨어 헤더의 경우 hh_cache 구조체로 arp_table 엔트리에 캐시
  • 일치하는 arp_table 엔트리가 없는 경우
    • ARP 요청 메시지를 브로드캐스트로 전달하고 타이머를 실행
    • 새 arp_table 엔트리를 생성한 후, 주소 변환을 필요로 하는 패킷들을 엔트리의 리스트(sk_buff queue)에 추가
    • ARP 응답이 없다면, 여러번 요청을 재시도 (여전히 응답이 없으면 arp_table에서 엔트리는 제거되고 큐된 sk_buff 구조체는 실패로 처리 --> TCP는 성립된 TCP 연결을 통해 패킷을 재전송하려고 시도)
    • ARP 응답이 있다면, arp_table 엔트리는 완료된 것으로 표시되고 큐에서 sk_buff 구조체들을 제거
    • 하드웨어 주소는 각 sk_buff 구조체의 하드웨어 헤더에 기록됨
  • ARP 계층은 자신의 프로토콜 타입(ETH_P_ARP)을 커널 네트워크 자료구조에 등록하고, packet_type 구조체를 생성
  • ARP 계층은 네트워크 장치가 수신한 모든 ARP 패킷을 전달받으며, ARP 요청에는 자신의 IP 주소가 지정되어 있으면 반드시 응답
  • 수신한 장치의 device 구조체에 저장되어 있는 하드웨어 주소로 ARP 응답을 생성
  • 네트워크 구성은 시간에 따라 바뀔 수 있으며, IP 주소는 다른 하드웨어 주소로 재할당 될 수 있음. 예를 들어, 전화접속 서비스는 연결될 때마다 각각 다른 IP 주소를 배정 받음
  • arp_tables 캐시가 항상 가장 최근의 엔트리를 가질 수 있도록, 정기적인 타이머를 실행해 모든 엔트리들이 타임아웃되지 않았는지 검사. 만약 하나 이상의 캐시된 하드웨어 헤더를 가지는 엔트리들(다른 자료구조들과 의존 관계)은 제거되지 않도록 주의
  • arp_table 엔트리 중 몇몇은 영구적이며, 이들은 할당이 해제되지 않도록 별도로 표시됨
  • 각 엔트리는 커널 메모리에 저장되기 때문에 너쿠 커지지 않도록 크기가 최대값에 도달할 때마다 가장 오래된 엔트리들을 제거

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

IP 라우팅

  • IP 라우팅 함수는 특정 IP 주소를 목적지로 하는 IP 패킷의 다음 도착지를 결정
  • 목적지에 도착 가능 유무, 전송하는데 사용할 네트워크 장치, 장치의 성능 등에 관한 정보를 관리하는 IP 라우팅 DB가 2개 존재
  • 전달 정보 데이터베이스(Forwarding Information Database) IP 주소와 알려진 루트(route)의 목록을 저장
    • 각 IP 서브넷은 fib_zone 구조체로 표현되며, 이들 모두는 fib_zones 해시 테이블에서 참조
    • 해시 값은 IP 서브넷 마스크로 생성되며, 동일한 서브넷으로의 모든 루트들은 fib_node 쌍으로 기술됨
    • fib_info 구조체는 각 fib_zone 구조체의 fz_list 큐에 추가됨
    • 만약 이 서브넷에 있는 루트 개수가 많아지면, fib_node 구조체를 쉽게 찾도록 해시 테이블이 생성됨
    • 서브넷으로 가는 루트가 여러 개 있다면, 각 루트는 다른 게이트웨이를 사용해야 함
    • 한 루트의 거리는 도달해야 하는 서브넷까지 거쳐야 하는 IP 서브넷의 개수를 의미 (이 값이 클수록 좋지 않은 루트)
    • 루트는 BSD 소켓 인터페이스로 IOCTL 요청을 보내서 추가되거나 제거 될 수 있음
      • 네트워크 구성이 시간이 지남에 따라 바뀌면 루트도 동적으로 변할 수 있음
    • 슈퍼유저 권한을 프로세스만이 INET 프로토콜 계층에서 IP 루트를 추가 및 제거 할 수 있음
    • 대부분 시스템은 라우터가 아닌 단말 시스템의 경우, 고정된 루트를 사용
    • 라우팅 프로토콜은 GATED 같은 데몬으로 구현되어 있으며, IOCTL 요청을 보내서 루트를 추가하거나 삭제

  • 목적지로 가는 루트를 빨리 찾기 위해, 더 작고 더 빠른 DB인 루트 캐시(route cache)를 사용
    • 루트 캐시는 자주 접근하는 루트에 대한 것들만 저장하며, ip_rt_hash_table 자료구조로 표현
    • 이 해시 테이블은 rtable 구조체의 리스트에 대한 포인터를 가진 배열로, 해시 값은 IP 주소 하위 두 바이트로 계산됨
    • 두 바이트는 목적지마다 다르기 때문에 해시 값을 가장 잘 분산시켜줌
    • 각 rtable 엔트리는 루트에 대한 정보를 포함: 목적지 IP 주소, 이 주소에 도달하는데 사용할 네트워크 장치, 메시지의 최대 크기, 참조 횟수, 사용 횟수, 타임스탬프 등
    • 참조 횟수는 이 루트가 사용될 때마다 증가하는 값으로, 이 루트를 사용하는 네트워크 연결의 개수를 의미. 프로세스가 이 루트로 데이터를 주고받지 않게 되면 감소
    • 사용 횟수는 이 루트를 발견할 때마다 증가하며, rtable 리스트에서 엔트리의 순서를 결정하는데 사용
    • 타임스탬프는 마지막으로 사용한 시간 값으로, 엔트리가 너무 오래되지 않았는지 검사하는데 사용
  • IP 루트를 조회하면 일치하는 루트를 찾기 위해 루트 캐시를 먼저 검사
  • 루트 캐시에 일치하는 루트가 없다면, 전달 정보 데이터베이스에서 루트를 탐색
  • 어떤 루트도 찾을 수 없다면, IP 패킷은 전송 실패로 처리되어 프로세스에게 전달됨
  • 루트가 전달 정보 데이터베이스에 있고 루트 캐시에 없다면, 이 루트에 해당하는 새 엔트리를 만들어 루트 캐시에 추가
  • 루트 캐시는 LRU 방식으로 동작: 최근에 많이 사용되지 않은 엔트리는 루트 캐시에서 제거하고, 만약 찾는 루트가 캐시에 있다면 리스트 맨 앞에 오도록 배치 (가장 최근에 많이 접한 엔트리가 항상 앞에 오도록 함)

 

본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


리눅스 파일 시스템

  • 리눅스는 각 파일 시스템이 계층적인 트리 구조로 통합해서 나타내므로, 파일 시스템이 하나인 것처럼 보여줌
    • 윈도우즈는 드라이브 이름 등의 장치 식별자로 구분
    • 새로운 파일 시스템을 마운트하면 하나의 파일 시스템에 추가된 형태로 보여짐
    • 파일 시스템은 로컬 시스템이 아닌 네트워크 연결로 원격지에서 마운트된 디스크도 포함 가능
  • 모든 파일 시스템은 어떤 타입이든지 하나의 디렉토리에 마운트되어, 마운트된 파일 시스템의 파일들이 그 디렉토리의 내용을 구성
    • 이는 많은 파일 시스템을 지원할 수 있게 함
    • 이러한 디렉토리를 마운트 디렉토리 또는 마운트 포인트라고 부름
  • 파일 시스템의 마운트가 해제되면, 마운트 디렉토리가 원래 가지고 있던 파일들이 보여짐
  • 디스크가 초기화될 때, 파티션 구조를 가지며 물리 디스크를 논리적으로 나누는 작업을 파티셔닝(partitioning)이라고 함
  • 각 파티션은 하나의 파일 시스템을 가지며, 파일 시스템은 장치의 블럭에 파일을 디렉토리나 소프트 링크(윈도우즈의 바로가기 같은) 등과 함께 논리적인 계층 구조로 구성 (블럭 장치만이 파일 시스템을 저장할 수 있음)
  • 파일 시스템은 일련의 블럭 장치들의 집합이며, 운영체제는 물리 디스크가 어떤 형태(구조)인지 알 필요가 없음
    • 하드웨어의 추상화 단계로 물리 장치의 세부 사항을 알 필요가 없음
    • 블럭을 읽는 요청은 각 블럭 디바이스 드라이버의 책임(블럭이 읽어야 하는 위치를 매핑)으로, 파일 시스템은 어떤 장치에 블럭이 있는지 상관없이 동작함
  • 파일(file)이란 데이터의 집합이며, 파일에 담긴 데이터 외에 파일 시스템의 구조도 포함되는데, 이러한 정보들은 신뢰성있게 저장되어야 하며 파일 시스템은 무결성을 보장해야 함
  • 처음에 제안된 파일 시스템은 확장 파일 시스템(Extended File System, EXT)으로, 가상 파일 시스템(Virtual File System, VFS)이라는 인터페이스 계층을 통해 실제 파일 시스템이 운영체제와 운영체제의 서비스로부터 분리됨
    • 즉, 파일 시스템의 세부 사항들이 소프트웨어에 의해 변환되는 것이고, VFS를 지원하는 파일 시스템들은 모두 사용 가능
  • VFS는 마운트되어 사용중인 각 파일 시스템의 정보를 메모리에 캐싱하며, 파일이나 디렉토리가 생성, 삭제, 또는 자료가 입력될 때 캐시 안의 자료를 정확하게 수정함. 이는 메모리 내 캐시와 디스크의 내용의 일치성을 보장함
    • 이 과정에서 디바이스 드라이버에 의해 접근하려는 파일이나 디렉토리 관련 자료구조가 생성되거나 제거됨
  • 버퍼 캐시(Buffer Cache)는 각 파일 시스템이 블럭 장치에 접근할 때 사용되며, 블럭에 접근할 때마다 그 블럭은 버퍼 캐시에 들어가고 상태에 따라 다양한 큐에 추가됨. 버퍼 캐시는 데이터 버퍼를 캐시할 뿐만 아니라, 블럭 디바이스 드라이버와의 비동기적인 인터페이스 관리에도 도움이 됨.
  • EXT가 성능이 떨어지면서 2차 확장 파일 시스템(EXT2)가 등장

https://stackoverflow.com/questions/34253611/are-device-files-implemented-by-the-specific-file-systems-or-the-virtual-file-sy
공통 파일 시스템 인터페이스
https://scslab-intern.gitbooks.io/linux-kernel-hacking/content/chapter13.html

2차 확장 파일 시스템(Second Extended File System, EXT2)

EXT2 파일 시스템의 물리적 배치도

  • EXT2의 파일에 저장된 데이터는 블럭에 저장되는데, 데이터 블럭의 크기는 동일하며, 서로 다른 EXT2의 경우 크기가 다를 수 있음
  • 블럭 크기는 파일 시스템이 만들어질 때 결정되며, 모든 파일의 크기는 블럭의 크기에 따라 올림이 됨
    • 예를 들어, 블럭 크기가 1024바이트이고, 파일 크기가 1025바이트일 때, 이 파일은 1024바이트 블럭 2개를 차지함
    • 실제로 파일 하나당 평균 블럭 크기의 절반을 낭비하는데, 이는 CPU의 메모리 사용량과 디스크 공간의 활용도 사이에 트레이드 오프(trade off) 문제로, 리눅스는 CPU 부담을 줄이는 방향으로 디스크 공간의 활용도를 희생함
  • 파일 시스템의 모든 블럭이 데이터만 저장하는 것은 아니며, 몇몇 블럭에는 파일 시스템의 구조를 표현하는 정보가 있음
  • 파티션은 하나의 파일 시스템을 가지는데, 파일 시스템은 공간을 블럭 그룹으로 쪼개고, 각 블럭 그룹은 파일 시스템에서 무결성을 보장해야 하는 정보를 중복해서 갖고 있어 파일 시스템 복구 시 중복 정보를 이용
  • EXT2 inode
    • EXT2는 파일 시스템 배치도를 정의하기 위해 시스템 내의 각 파일을 inode 자료구조로 표현하며, 모든 inode는 inode 테이블에 저장되고, 파일은 일반 파일, 디렉토리, 소프트 링크, 하드 링크, 장치 파일을 포함
    • 즉, 디렉토리도 inode로 기술되며, 디렉토리에 속하는 파일들의 inode 포인터를 디렉토리 inode에서 보관
    • inode는 파일의 데이터가 위치한 블럭과 파일에 대한 접근 권한 및 파일 수정 시간, 파일 종류 등의 정보를 가지며, EXT2의 모든 파일은 각각 하나의 inode와 대응 (즉, inode는 구분 가능한 고유 번호를 가지고 식별됨)
    • Mode 접근 권한 정보 및 파일의 유형(일반 파일, 디렉토리, 심볼릭 링크, 블럭 장치, 문자 장치, FIFO)
    • Owner Information 해당 파일의 소유자 및 그룹 식별자
    • Size 바이트 단위의 파일 크기
    • Timestamp inode가 만들어진 시각과 최종 수정 시각
    • Datablocks 데이터가 저장된 블럭에 대한 포인터, 맨 앞 12개의 포인터는 데이터를 저장한 실제 블럭에 대한 포인터이고 마지막 3개는 점점 더 높은 수준의 간접 연결(블럭의 포인터의 포인터)을 포함.
    • 특수 장치 파일은 실제 디스크에 존재하지 않는 파일이지만 inode로 표현되며, 커널 내부에는 장치를 접근하기 위한 코드가 존재

  • EXT2 Superblock
    • 수퍼블럭은 파일 시스템의 기본 크기나 모양에 대한 설명을 기술하는 자료구조로, 이 정보를 이용하여 관리자가 파일 시스템을 활용하고 유지
    • 보통 파일 시스템이 마운트 될 때, 블럭 그룹 0에 있는 수퍼블럭을 읽어들이지만, 모든 블럭 그룹에는 동일한 복사본이 존재해 파일 시스템이 깨지는 경우를 대비함
    • Magic Number 마운트 프로그램에 EXT2의 수퍼블럭이라는 것을 알리는 용도 (=0xEF53)
    • Revision Level 메이저 개정 레벨과 마이너 개정 레벨로 구성되며, 마운트 프로그램이 어떤 특정 버전에서만 지원되는 기능이 현재 파일 시스템에서 지원되는지 확인하는 용도. 또한 마운트 프로그램이 파일 시스템에서 안전하게 사용가능한 기능 목록(기능 호환성 항목)을 판단할 때 사용
    • Mount Count & Maximum Mount Count 파일 시스템 전부를 검사할 필요가 있는지 확인할 때 사용. 마운트 횟수는 파일 시스템이 마운트될 때마다 1씩 증가하며, 그 값이 최대 마운트 횟수에 도달하면 e2fsck를 실행하라는 메시지가 출력됨
    • Block Group Number 현재 수퍼블럭 복제본을 가지는 블럭 그룹의 번호
    • Block Size 바이트 단위의 블럭 크기
    • Blocks per Group 그룹당 블럭 수 (파일 시스템 만들 때 지정됨)
    • Free Blocks 파일 시스템 내 프리 블럭 수 = 사용 가능한 블럭 수
    • Free Inode 파일 시스템 내 프리 inode 수
    • First Inode 첫번째 inode 번호, 루트 파일 시스템에 루트 디렉토리를 나타냄

  • EXT2 Group Descriptor
    • 각 블럭 그룹은 자신을 기술하는 자료구조를 가지며, 수퍼블럭처럼 모든 블럭 그룹을 위한 그룹 기술자는 각 블럭 그룹에 복제되어 있어 파일 시스템이 깨지는 경우를 대비 (실제로 블럭 그룹 0에 있는 첫번째 복사본만 사용됨)
    • Block Bitmap 블럭 그룹에서 블럭의 할당 상태를 나타내는 비트맵 (블럭 수만큼 존재, 블럭 할당 및 해제 시 사용)
    • Inode Bitmap 블럭 그룹에서 inode의 할당 상태를 나타내는 비트맵 (블럭 수만큼 존재, inode 할당 및 해제 시 사용)
    • Inode Table 블럭 그룹의 inode 테이블의 시작 블럭 (블럭 수만큼 존재, 각 inode는 EXT2 inode 구조체에 의해 표현)
    • Free Blocks Count & Free Inode Count 
    • Used Directory Count 
    • 그룹 기술자는 전체적으로 하나의 그룹 기술자 테이블을 형성하며, 각 블럭 그룹에는 수퍼블럭 뒤에 그룹 기술자 테이블이 존재
  • EXT2 Directory
    • 디렉토리는 파일에 대한 접근 경로를 생성하고 저장하는 특별한 파일
    • 메모리 상에서 디렉토리 파일은 디렉토리 엔트리의 리스트
    • 디렉토리 엔트리는 디렉토리에 저장된 파일과 1:1 대응이며, 모든 디렉토리에서 첫 2개의 엔트리는 각각 .과 ..으로 현재 디렉토리와 부모 디렉토리를 의미
    • Inode 엔트리에 해당하는 Inode로, 이 값은 블럭 그룹의 inode 테이블에 저장되어 있는 inode 배열에 대한 인덱스
    • name length 바이트 단위의 파일 이름 길이
    • name 파일 이름

디렉토리 엔트리

EXT2 파일 연산

  • 파일 탐색
    • 파일 이름은 길이 제한이 없고 인쇄 가능한 문자면 가능. 단, 전체 경로에서 파일을 구분하기 위해 슬래시(/)를 사용
    • 파일을 나타내는 inode를 찾기 위해 시스템은 파일 이름을 해석해서 한 디렉토리씩 처리하면서 마지막 슬래시 뒤에 있는 이름을 가진 파일을 얻음
    • 루트 디렉토리의 inode 번호는 파일 시스템의 수퍼블럭에서 얻으며, 이 번호의 파일을 읽기 위해 해당 블럭 그룹의 inode 테이블을 이 번호로 인덱싱하여 엔트리를 얻음
    • 정리하자면, 전체 경로에서 파일을 찾을 때까지 디렉토리 엔트리를 반복적으로 찾아가면서 마지막 슬래시 뒤에 이름을 갖는 파일의 데이터 블럭을 얻음
  • 파일 크기 변경
    • 파일 시스템은 데이터를 가지고 있는 블럭들이 분할되는 경향(디스크 조각화)이 있는데, EXT2의 경우 어떤 파일에 대한 새로운 블럭을 현재 데이터 블럭들에 인접하도록 할당하거나 적어도 현재 블럭 그룹과 같은 그룹에 할당하는 것으로 이 문제를 극복
      • 둘 다 실패하면, 다른 블럭 그룹에 있는 데이터 블럭을 할당 (어쩔 수 없음)
    • 프로세스가 파일에 데이터를 쓰려고 할 때마다, 파일 시스템은 데이터가 파일에 마지막으로 할당된 블럭을 넘어가는지 검사하고, 넘어가면, 이 파일을 위한 새로운 데이터 블럭을 할당 (프로세스는 할당하고 기록이 끝날 때까지 대기)
      • 블럭 할당 루틴은 파일 시스템의 수퍼블럭에 락을 걸어 수퍼블럭에 있는 항목을 변경. 이는 둘 이상의 프로세스가 파일 시스템을 동시에 변경하는 것을 막기 위한 것으로, 다른 프로세스가 블럭을 할당하려고 하면 현재 프로세스는 대기
      • 수퍼블럭의 사용은 요청 순서에 따르며, 한 프로세스가 제어를 갖게 되면 작업을 종료할 때까지 제어를 가짐(원자적)
      • 수퍼블럭에 락을 걸고 나면, 프리 블럭(사용가능한 블럭)이 충분한지 검사. 만약 충분하지 않다면 할당 루틴은 실패하고 프로세스는 수퍼블럭에 대한 제어권을 양도
      • 만약 프리 블럭이 충분하다면, 프로세스는 새 블럭을 할당 받고 수퍼블럭에 대한 제어권을 양도
      • 미리 할당된 블럭을 사용할 수 있는데, 실제 존재하지는 않으며 단지 할당된 블럭 비트맵에 예약되어 있음
    • inode에는 새로운 데이터 블럭을 할당하기 위한 항목 2개가 존재
      • prealloc_block 처음에 미리 할당된 데이터 블럭 수
      • prealloc_count 그 중에서 남은 개수
    • 할당할 때는 마지막 데이터 블럭의 다음 데이터 블럭이 비었는지 검사해서 비었으면 할당하고 안비었으면 검색 범위를 넓혀 가까운 블럭에서 64블럭 이내의 데이터 블럭을 살펴봄 (순차 접근을 빠르게 하기 위함)
      • 대부분 그 파일에 속한 다른 데이터 블럭과 같은 블럭 그룹
      • 빈 블럭이 없다면 계속 탐색하는데, 한 블럭 그룹 안에 8개의 빈 데이터 블럭으로 된 덩어리를 찾으려고 함
      • 만약 블럭 미리 할당 기능이 필요하고 사용 가능할 경우, prealloc_block 과 prealloc_count 값을 각각 갱신
    • 빈 블럭을 찾을 때마다 블럭 그룹의 블럭 비트맵을 갱신하고 버퍼 캐시 내에 데이터 버퍼를 할당
      • 데이터 버퍼는 파일 시스템을 지원하는 장치 식별자와 할당된 블럭의 번호에 의해 식별됨
      • 버퍼 내의 데이터가 모두 0이고 버퍼가 더티(dirty)라고 표시되어 있으면 실제 디스크에 내용이 기록되지 않은 것
    • 수퍼블럭을 기다리는 프로세스가 있으면 큐에 있는 프로세스 중 첫번째 프로세스가 다시 실행되며 파일 처리에 필요한 수퍼블럭을 독점

가상 파일 시스템(Virtual File System, VFS)

VFS 구조

  • 전체 파일 시스템과 특정 마운트된 파일 시스템을 기술하는 자료구조를 관리하기 위한 인터페이스로, EXT2와 비슷한 방법으로 시스템에 있는 파일을 수퍼블럭과 inode로 표현 (VFS inode는 EXT2 inode처럼 시스템에 있는 파일과 디렉토리를 나타냄)
  • 각 파일 시스템들은 부팅 중 초기화될 때, 자신을 VFS에 등록하여 커널에 파일 시스템이 포함될 수 있으며, 특정 파일 시스템을 마운트하는 경우 모듈에 의해 VFS에 등록됨
  • 블럭 장치에 기반한 파일 시스템이 마운트되었고, 루트 파일 시스템이 존재한다면, VFS는 이것의 수퍼블럭을 읽어와 해당 파일 시스템의 배치도를 VFS 수퍼블럭 자료구조에 매핑시킴
  • VFS는 마운트된 파일 시스템과 VFS 수퍼블럭의 리스트를 관리하며, 각각의 VFS 수퍼블럭은 특정 기능을 수행하는 루틴에 대한 정보와 포인터 및 파일 시스템의 첫번째 VFS inode에 대한 포인터를 포함 (루트 파일 시스템의 경우 이 inode는 루트 디렉토리의 것)
    • 예를 들어, 마운트된 파일 시스템을 나타내는 수퍼블럭은 고유의 inode 읽기 루틴에 대한 포인터를 가지며, 호출 시 적절한 파일 시스템의 블럭을 읽을 수 있게 됨
  • 프로세스가 파일이나 디렉토리에 접근할 때, VFS inode를 탐색하는 시스템 루틴이 호출되며, 수많은 inode가 반복적으로 접근
    • 접근 속도를 빠르게 하기 위해 inode는 캐시에 저장됨
    • 어떤 inode가 캐시에 존재하지 않으면, 해당 inode를 읽기 위해(디스크->메모리) 각 파일 시스템 고유의 루틴을 호출
    • 읽어들인 inode는 캐시에 저장되어 다음번 접근 시 캐시에서 탐색됨
    • 덜 사용되는 VFS inode는 캐시로부터 제거 (LRU Cache)
  • 모든 파일 시스템은 실제 장치에 대한 접근 속도를 높이기 위해 버퍼 캐시(Buffer Cache)를 사용
    • 같은 데이터가 자주 필요할 때를 대비하여 디스크 접근 횟수를 줄임
    • 버퍼 캐시는 파일 시스템과는 상호 독립적(비동기 요청 인터페이스를 제공)이며, 커널이 데이터 버퍼를 할당하고 읽고 쓰는 메커니즘에 통합되어 있음
  • inode를 읽기 위해, 커널은 블럭 디바이스 드라이버에게 제어하는 장치로부터 블럭을 읽도록 요청하여 디스크에서 데이터를 가져옴
    • 이러한 블럭 장치 인터페이스는 버퍼 캐시에 통합되어 있음 (버퍼 캐시에서 데이터가 없는 경우 즉시 호출)
  • 파일 시스템이 어떤 블럭을 읽으면 그 블럭은 버퍼 캐시에 저장되어 모든 파일 시스템과 커널에 의하여 공유되며, 버퍼 캐시에 있는 각 데이터 버퍼는 블럭 번호와 그 블럭을 읽은 장치의 고유 식별자에 의하여 구분됨
  • 자주 사용되는 디렉토리의 inode를 빨리 찾기 위해 디렉토리 캐시도 제공, 이 캐시는 전체 디렉토리 이름과 해당되는 inode 번호와의 매핑을 저장하며 디렉토리 자체에 대한 inode는 inode 캐시에 저장됨

http://www.haifux.org/lectures/119/linux-2.4-vfs/linux-2.4-vfs.html

  • VFS Super Block
    • device 파일 시스템이 저장되어 있는 블럭 장치의 식별자
    • mounted inode 파일 시스템의 첫번째 inode를 참조하는 포인터
    • covered inode 파일 시스템이 마운트된 디렉토리를 표현하는 inode를 참조하는 포인터
      • 루트 파일 시스템의 VFS 수퍼블럭은 covered inode 가 없음
    • block size 바이트 단위의 블럭 크기
    • superblock operations 마운트된 파일 시스템의 file_system_type 구조체를 참조하는 포인터
    • file system specific 파일 시스템이 필요로 하는 정보를 참조하는 포인터
  • VFS inode
    • 각 VFS inode의 정보는 파일 시스템의 정보로부터 파일 시스템 고유 루틴에 의해 생성되며, VFS inode는 커널 메모리에만 존재하고 시스템이 필요한 동안 VFS inode 캐시에 저장됨
    • device 해당 VFS inode가 나타내는 파일 또는 장치의 식별자
    • inode 파일 시스템 안에서 유일한 inode 번호, 장치와 inode 번호의 조합은 VFS 내에서 유일
    • mode VFS inode가 해당하는 파일의 종류(파일, 디렉토리, 기타 등)와 적븐 권한 등을 나타냄
    • user id 파일의 소유자
    • times 생성 시각, 변경 시각, 읽은 시각 등을 나타냄
    • block size 바이트 단위의 블럭 크기
    • inode operations 연산 루틴의 주소들에 대한 포인터 (만약 0이면 inode가 프리되어 제거되거나 재사용됨)
    • count VFS inode를 현재 사용하는 시스템 요소(프로세스 등)의 수 (만약 0이면 inode가 프리되어 제거되거나 재사용됨)
    • lock VFS inode를 락 걸기 위해 사용 (inode를 읽을 때)
    • dirty VFS inode가 기록되어 파일 시스템 아래 계층에서 변경이 필요한지 나타냄
    • file system specific information

 

VFS 자료구조
https://myaut.github.io/dtrace-stap-book/kernel/fs.html
https://www.programmersought.com/article/50821832993/

VFS 연산

  • 파일 시스템 등록
    • 커널을 빌드할 때 지원할 파일 시스템을 지정할 수 있음.
    • 파일 시스템 시작 코드는 내장된 모든 파일 시스템의 초기화 루틴을 호출
    • 파일 시스템 모듈은 로드될 때마다 자신을 커널에 등록하고, 언로드될 때 자신을 커널로부터 해제함
    • 각 파일 시스템의 초기화 루틴은 자신을 VFS에 등록하며, file_system_type 구조체로 표현
    • 이 구조체에는 파일 시스템의 이름과 VFS 수퍼블럭 읽기 루틴에 대한 포인터가 저장되어 있음
    • *read_super() 파일 시스템이 마운트될 때 VFS에 의해 호출
    • name e.g., ext2
    • required_dev 이 파일 시스템을 실제로 지원하는 장치가 필요한지를 나타냄
    • 어떤 파일 시스템이 등록되어 있는지는 /proc/filesystems 파일 내용을 출력해서 알 수 있음

  • 파일 시스템 마운트
    1. 수퍼유저가 파일 시스템을 마운트하려고 할 때, 커널은 시스테 콜로 전달된 인자가 옳은지 파악
      • e.g., mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom
      • 커널에 3가지 정보를 전달: 파일 시스템 이름, 파일 시스템을 포함하고 있는 블럭 장치, 마운트할 위치
    2. VFS는 먼저 파일 시스템 리스트(file_systems)를 탐색하여 각 file_system_type 구조체를 살펴보고, 인자로 넘어온 이름의 구조체를 발견하면 커널에서 지원하는 타입인지 검사
      • 일치하는 것을 찾지 못하더라도, 커널이 그 파일 시스템의 모듈을 요구시 로드하도록 빌드되었다면 커널 데몬이 모듈을 로드
    3. 새로운 파일 시스템의 마운트 지점이 될 디렉토리의 VFS inode를 탐색
      • 이 VFS inode는 캐시에 있거나, 마운트 지점의 파일 시스템을 저장하고 있는 블럭 장치에서 읽어옴
    4. inode를 찾으면 디렉토리인지 혹은 이미 다른 파일 시스템이 마운트되었는지 검사
      • 한 디렉토리는 단 하나의 파일 시스템의 마운트 지점으로만 사용 가능
    5. 새로운 VFS 수퍼블럭을 할당하고, 이를 마운트 정보와 함께 해당 파일 시스템을 위한 수퍼블럭 읽기 루틴에 전달
      • 모든 VFS 수퍼블럭은 super_block 구조체의 배열인 super_blocks에 저장됨
      • 이번 마운트를 위해 super_block 구조체는 하나만 할당됨
    6. 수퍼블럭 읽기 루틴은 물리 장치에서 읽은 정보에 따라 VFS 수퍼블럭을 채워넣음
      • 만약 그 파일 시스템의 블럭 장치로부터 읽지 못한다면(블럭 장치가 해당 파일 시스템의 타입으로 사용 불가한 경우) 마운트 명령은 실패
    7. 마운트된 각 파일 시스템은 vfsmount 구조체로 기술되며, 이들은 vfsmntlist 가 참조하는 리스트에 추가됨
      • vfsmnttail 포인터는 리스트의 마지막 항목을 가리키고, mru_vfsmnt 포인터는 가장 최근에 사용된 파일 시스템을 참조
      • 각 vfsmount 구조는 파일 시스템을 담고 있는 블럭 장치의 장치 번호, 이 파일 시스템이 마운트된 디렉토리, 마운트될 때 할당된 VFS 수퍼블럭에 대한 포인터 등을 포함

마운트 커널 자료구조

  • 파일 시스템 마운트 해제
    • 어떤 프로세스도 마운트된 디렉토리나 그 하위 디렉토리를 사용하지 않는 상태에서만 마운트 해제 가능
    • 사용중이면 VFS inode 캐시에 해당 파일 시스템이 속하는 VFS inode가 존재하기 때문에, 캐시의 inode 리스트를 검사
    • 마운트된 파일 시스템의 VFS 수퍼블럭이 더티(dirty) 상태이면, 수퍼블럭을 디스크에 기록
    • 디스크 기록 후, VFS 수퍼블럭이 차지하고 있던 메모리를 커널 메모리 풀(memory pool)에 보내고, 마지막으로 이 파일 시스템의 마운트에 필요했던 vfsmount 구조체를 vfsmntlist 리스트에서 제거한 뒤 메모리 해제

VFS 캐시

  • VFS inode 캐시
    • VFS는 마운트된 파일 시스템에 대한 접근 속도를 높이기 위해 해시 테이블로 구현된 inode 캐시를 유지
    • inode를 접근할 때마다 VFS inode 캐시를 먼저 탐색
    • 해시 테이블 내의 각 엔트리는 같은 해시 값을 갖는 VFS inode 리스트를 참조
    • 해시 값은 inode 번호와 그 파일 시스템을 갖는 장치의 장치 식별자로 계산됨
    • VFS inode 를 얻으려면 찾으려는 것과 같은 inode 번호와 장치 식별자를 가진 inode를 찾을 때까지 리스트를 순회
    • 만약 inode가 있으면, 카운트 값을 증가시키면서 그 inode를 사용하는 프로세스가 있음을 알리고 파일 시스템에 접근
    • 만약 찾을 수 없다면, 파일 시스템이 메모리로 파일을 가져오기 위해 빈 VFS inode를 탐색
    • VFS inode를 더 할당할 수 있으면, 커널 페이지를 할당하고 이를 여러 새 빈 inode로 쪼갠 뒤, 할당된 VFS inode는 first_inode가 가리키는 리스트와 캐시의 리스트에 추가됨
    • VFS inode를 더 할당할 수 없으면, 재사용할만한 inode 후보들을 탐색 (사용횟수가 0인 inode는 좋은 후보)
      • 만약 VFS inode가 더티 상태이면 파일 시스템에 그 내용을 기록
      • 만약 락이 걸려 있으면 풀릴 때까지 대기
      • 재사용 후보가 선택되면 내용을 지움
    • 할당된 VFS inode는 파일 시스템에서 읽어온 정보를 채우는 루틴이 호출되며, 카운트 값은 1이 되고 락이 걸림
      • 그 inode가 완전한 정보를 가지기 전까지 아무도 접근 할 수 없음
    • VFS inode 캐시가 사용되어 꽉 차면, 덜 사용되는 inode는 버려지고 더 많이 사용되는 것들만 남음 (LRU Cache)
  • 디렉토리 캐시
    • VFS는 디렉토리에 대한 접근 속도를 높이기 위해 해시 테이블로 구현된 디렉토리 엔트리에 대한 캐시를 유지
    • 디렉토리는 실제 파일 시스템에 의하여 참조되므로, 그 내용도 디렉토리 캐시에 저장됨
    • 짧은 이름(최대 15자)을 가진 디렉토리 엔트리만 캐시되며, 이는 짧은 것이 더 자주 사용되기 때문
    • 캐시의 테이블 엔트리는 같은 해시 값을 가진 디렉토리 캐시 엔트리들의 리스트를 참조
    • 해시 함수는 파일 시스템을 갖고 있는 장치의 장치 번호 및 디렉토리 이름을 사용하여 해시 테이블 내의 인덱스를 산출
    • 캐시 값을 유효하게 하고 최신 값을 유지하는 LRU 방식으로 디렉토리 캐시 엔트리 리스를 관리
      • 디렉토리 엔트리는 처음에 참조되면 1단계 LRU 리스트의 맨 뒤에 추가
      • 1단계 리스트가 꽉 차서 자리가 없으면, 이 리스트의 맨 앞 엔트리를 제거 (가장 적게 사용된 것)
      • 디렉토리 엔트리가 다시 참조되면 2단계 LRU 리스트로 엔트리를 옮김
      • 2단계 리스트가 꽉 차서 자리가 없으면, 이 리스트의 맨 앞 엔트리를 제거

https://www.researchgate.net/figure/The-Linux-original-path-lookup-mechanism-The-block-means-a-dentry-structure-and-p_fig1_325982529
https://scslab-intern.gitbooks.io/linux-kernel-hacking/content/chapter13.html

  • 버퍼 캐시
    • 디스크에서 데이터 블럭을 읽고 쓸 때 디스크 접근 횟수를 줄이기 위해 해시 테이블로 구현된 블럭 버퍼에 대한 캐시
    • 파일 시스템이 데이터 블럭을 읽고 쓰는 요청을 보내면, 표준 커널 함수를 호출하여 블럭 디바이스 드라이버에게 buffer_head 구조체를 전달. 이 구조체는 블럭 디바이스 드라이버가 필요로 하는 모든 정보를 제공
      • 장치 식별자는 장치를 유일하게 구별해주고, 블록 번호는 읽어야 할 위치를 알려줌
    • 모든 블럭 버퍼들은 새 것이거나 안 쓰이거나 상관없이 버퍼 캐시 어딘가에 존재하며, 모든 블럭 장치들이 이 캐시를 공유
    • 지원하는 버퍼 크기별로 각각 하나의 리스트가 존재하며, 시스템의 프리 블럭 버퍼는 처음 만들어질 때나 블럭이 해제될 때 이 리스트에 추가됨 (현재 지원하는 버퍼 크기는 512, 1024, 2048, 4096, 8192바이트)
    • 버퍼 캐시는 똑같은 해시 값을 가지는 버퍼들의 리스트를 참조하는 포인터들의 배열로, 해시 값은 블럭 버퍼를 소유하는 장치 식별자와 버퍼의 블럭 번호로 산출되며, 각 버퍼 유형마다 LRU 리스트가 있고 캐시에 추가되면 이 리스트에도 추가됨
      • LRU 리스트는 특정 유형의 버퍼에 대해 작업할 때 사용되며, 새로운 데이터를 가진 버퍼를 디스크에 기록
    • 버퍼의 유형은 버퍼의 상태를 반영한 것
      • clean 사용하지 않은 새 버퍼
      • locked 버퍼에 락이 걸려 있으며, 기록되기를 기다리는 버퍼
      • dirty 새롭고 유효한 데이터를 가지며, 언제 기록될지 스케줄되지 않은 버퍼
      • shared 공유 버퍼
      • unshared 이전에 공유되었으나 지금은 공유하지 않는 버퍼
    • 블럭 버퍼는 프리 리스트 중 어떤 하나의 리스트나, 버퍼 캐시 둘 중 하나에만 속함
    • 파일 시스템이 물리 장치로부터 버퍼를 읽을 때마다 가장 먼저 버퍼 캐시에 접근해서 블럭을 얻으려고 시도
      • 만약 버퍼 캐시에서 얻을 수 없다면, 프리 리스트에서 적당한 크기의 버퍼를 하나 얻고, 이는 버퍼 캐시에 추가됨
      • 만약 버퍼 캐시에서 얻을 수 있다면, 최근 것이 아니고 새로운 버퍼일 경우 파일 시스템은 디바이스 드라이버에게 해당하는 데이터 블럭을 디스크에 요청해서 읽어옴
    • bdflush 커널 데몬  버퍼 캐시를 사용하는 블럭 장치들 사이에 공평하게 캐시 엔트리를 할당하는 데몬
      • 시스템에 있는 더티 버퍼의 개수가 충분히 많아지기를 기다리며 잠들어 있다가 버퍼가 할당되거나 버려질 때 시스템에 있는 더티 버퍼의 개수를 검사
      • 전체 버퍼의 개수 중 더티 버퍼의 비율이 너무 커지면 bdflush 가 깨어나 BUF_DIRTY_LRU 리스트에 데이터 버퍼를 연결 (비율은 기본값이 60%이지만, 시스템에서 버퍼가 필요하다면 이 데몬은 언제든 깨어남)
      • bdflush는 이중에서 적당한 개수(디폴트 500)를 디스크에 기록
    • update 명령어 실행될 때마다 시스템에 있는 모든 더티 버퍼에서 시간이 만료된 것들만 디스크에 기록. 더티 버퍼가 다 쓰여지면 시스템 시간을 표시

버퍼 캐시 커널 자료구조

/proc 파일 시스템

  • 실제로 존재하는 것이 아니며, 내부 파일과 디렉토리를 커널에 있는 정보를 가지고 생성
  • 실제 파일 시스템과 마찬가지로 자신을 VFS에 등록하고 파일이나 디렉토리를 열면 VFS가 inode를 요청
  • 커널의 /proc/devices 파일은 장치들을 나타내는 커널 자료구조로부터 생성됨
  • /proc 파일 시스템은 사용자에게 커널 내부 작업을 볼 수 있는 뷰(view)를 제공
  • 커널 모듈같은 몇몇 리눅스 서브시스템들은 /proc 파일 시스템에 엔트리를 생성함

장치 특수 파일(Device Special File)

  • 리눅스는 하드웨어 장치들을 특수 파일로 저장하며, /dev/null의 경우 널 장치라 해서 윈도우즈의 휴지통과 같은 역할을 함
  • 장치 파일은 파일 시스템에서 어떤 데이터 영역도 차지하지 않으며, EXT2와 VFS는 모두 장치 파일을 inode의 특수한 유형으로 구현
  • 장치 파일에는 2가지 형태가 존재: 문자 특수 파일, 블럭 특수 파일
  • 문자 장치는 문자 모드로 I/O 작업을 할 수 있고, 블럭 장치는 모든 I/O가 버퍼 캐시를 통하도록 되어 있음
  • 장치 파일로 I/O를 요구하면, 시스템 내에 있는 해당 디바이스 드라이버가 요청을 받음
    • 디바이스 드라이버는 실제하지 않고 SCSI 디바이스 드라이버 같은 어떤 서브시스템을 위한 유사 디바이스 드라이버일 수 있음
  • 커널에서 모든 장치는 각각 kdev_t 구조체로 기술되고, 이는 2바이트 길이로 첫번째 바이트는 마이너 장치 번호, 두번째 바이트는 메이저 장치 번호를 의미. (장치의 유형을 구별하는 메이저 번호와, 장치 유형의 하나의 케이스를 구별하는 마이너 번호로 장치 파일이 참조됨)

 

+ Recent posts