linux-network

View the Project on GitHub super-learners/linux-network

Chapter 5. Network Device Initialization

네트워킹과 관련하여 초기화가 어떻게 이루어지는지에 대한 장입니다. 바로 본론으로

System Initialization

Kernel Initialization
Kernel Initialization
출처: Understanding Linux Network Internals

주목해야 할 포인트

  1. 부트타임 옵션
  2. IRQ, soft IRQ, timers 초기화
  3. 커널 서브시스템 및 빌트인 드라이버들을 초기화 – do_initcalls() -> free_init_mem()

run_init_process()init 프로세스를 결정

Device Registration and Initialization

디바이스가 작동하려면? -> 커널에 인식되어야 하고 올바른 드라이버랑 연결되어야 함

3단계

  1. Hardware initialization – 디바이스 드라이버 & 제네릭 버스 레이어 에 의해
  2. Software initialization – 어떤 네트워크 프로토콜을 사용하냐에 따라 유저가 파라미터를 명시해야 하는 경우 (ip 주소 등)
  3. Feature initialization –커널의 네트워킹 옵션 중 디바이스별 설정이 필요한 것들이 있음 (e.g. Traffic Control)

NIC Initialization

struct net_device에는 커널이 디바이스 드라이버와 통신하기 위해 이용하는 function pointer들이 있음 (Chapter2 참고) 이걸 초기화하는 것은 장치의 타입(이더넷 등), 브랜드, 모델에 따라 다름. 로직은 다 비슷하므로 이 책에서는 ethernet만을 다룸.

디바이스 드라이버는 device-kernel 커뮤니케이션을 위해 다음과 같은 리소스를 할당한다.

  1. IRQ 라인 – NIC는 필요할 때 커널의 주목을 받을 수 있도록 하기 위해 IRQ를 하나 할당받는다. (가상장치 제외)
  2. I/O ports and memory registration – 디바이스의 메모리를 시스템의 메모리에 맵핑해서, 디바이스 드라이버가 시스템 메모리에 read/write할 수 있도록 하는 기법이 자주 사용됨.

장치 - 커널 통신

뻔한 두 가지 방식

  1. 인터럽트
  2. 폴링

하드웨어 인터럽트

디바이스 드라이버는 irq를 요청한 후에 해당 irq에 인터럽트 핸들러를 등록/해제하기 위해 다음 두 함수를 사용함.

int request_irq(
    unsigned int irq,
    void (*handler)(int, void*, struct pt_regs*),
    unsigned long irqflags,
    const char * devname,
    void *dev_id
)

void free_irq(
    unsigned_int irq,
    void *dev_id
)

인터럽트를 통해 NIC가 드라이버에 알릴 수 있는 정보 예시

Interrupt Sharing

“Interrupt sharing”을 이용하면 하나의 IRQ에 여러 디바이스를 연결시킬 수 있음. 하지만 하나의 shared IRQ에 연결된 디바이스 드라이버가 모두 이 기능을 지원한다고 명시해야만 함.

IRQ 라인은 플랫폼에 따라 정해진 개수만큼만 있기 때문에 여러 디바이스가 하나의 IRQ를 사용하면 리소스를 절약할 수 있는 방법이 된다.

참고: IRQs - X86 Assembly/Programmable Interrupt Controller

Of the 15 usable IRQs, some are universally associated with one type of device:

IRQ 0 ‒ system timer
IRQ 1 — keyboard controller
IRQ 3 — serial port COM2
IRQ 4 — serial port COM1
IRQ 5 — line print terminal 2
IRQ 6 — floppy controller
IRQ 7 — line print terminal 1
IRQ 8 — RTC timer
IRQ 12 — mouse controller
IRQ 13 — math co-processor
IRQ 14 — ATA channel 1
IRQ 15 — ATA channel 2

따라서 디바이스 드라이버가 interrupt sharing을 지원한다는 얘기는 즉, 자신이 등록한 인터럽트 핸들러가 커널에 의해 invoke될 때 무시해도 좋은지 코드를 실행해야 하는지 판단하는 로직이 있다는 얘기임 (디바이스의 레지스트리 값을 보고 판단할 수 있게 되어있다든지)

핸들러 맵핑

irq_desc, setup_irq()는 arch 디렉토리 이하에 아키텍처에 맞게 오버라이딩되어있음.

Organization of IRQ handlers
Organization of IRQ handlers
출처: Understanding Linux Network Internals


끊고 갑시다


초기화 옵션

커널에 박혀있는 것과 모듈로 로드된 컴포넌트 모두 파라미터를 줄 수 있음. 여기에 쓰이는 두 가지 매크로가 있는데

  1. Module options (module_param)

모듈을 로드할 때 옵션을 줄 수 있게 한 매크로. 커널에 박아 넣은 컴포넌트에 대해서는 이 옵션의 값들을 부트타임에 줄 수 없음. (참고: /sys 인터페이스)

  1. Boot-time kernel options (_ _setup)

부트로더를 통해서 부트타임에 줄 수 있는 옵션들을 정의.

두 방식이 다 사용되기 때문에, 커널 부트타임에 정의된 옵션 이름과 모듈 로드타임에 정의된 옵션 이름이 겹칠 수도 있음. 모듈 로드 시 옵션 이름은 해당 모듈에만 적용되는 것임.

모듈 옵션

pass 커널 모듈은 파라미터를 어떻게 정의할까? -> module_param 매크로를 사용

...
module_param(multicast_filter_limit, int 0444);
module_param(max_interrupt_work, int, 0444);
module_param(debug, int, 0444);
              ^      ^    ^
//          이름  자료형 /sys 파일 퍼미션
...

Understanding Linux Networking Internals에 있는 drivers/net/sis900.c의 예

/sys/modules/[module name]/parameters 에 ls 쳐보면 나옴. 파일 퍼미션을 어떻게 줬느냐에 따라서 읽기, 쓰기가 가능한데 퍼미션을 0으로 주면 아예 /sys에 안나온다. 덮어 쓸 경우 값이 변했다는 노티를 줄 수 있는 방법이 없기 때문에, 모듈이 옵션값의 변화를 감지하는 방법을 만들거나, 중간에 값이 바뀌어도 문제가 없도록 짜야 함.

Initializing the Device Handling Layer

Traffic Control, per-CPU ingress queues를 포함한 중요한 초기화가 net/core/dev.c에서 일어남.

static int _ _init net_dev_init(void)
{
    ...
}
subsys_initcall(net_dev_init);

net_dev_init()에서 일어나는 일

랜덤값은 타이머에 주어져서 타이머들이 동시에 실행되어 CPU에 부하를 주는 것을 막거나, 특정 data structure의 구조를 알아내려는 DoS 공격을 방지하는 데 쓰일 수 있음.

User-Space Helpers

Virtual Devices

Tuning via /proc Filesystem