Linux v6.18.37 · 개념과 코드 읽기

net_device: 등록한 인터페이스를 실제로 켜는 절차

이 코드는 어떤 문제를 푸나요?

네트워크 인터페이스가 목록에 보이는 것과 패킷을 주고받을 준비가 된 것은 다릅니다. net_device는 장치의 공통 상태와 드라이버 연산을 연결합니다. __dev_open 전체를 따라가면 부모 장치의 전원 상태, 주소 검사, 드라이버 시작 함수, 큐 활성화가 서로 다른 단계라는 점을 알 수 있습니다.

읽을 범위: v6.18.37 · net/core/dev.c · __dev_open 1660–1709행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

등록과 UP

등록은 네트워크 장치를 커널이 관리하게 만드는 일이고 UP은 사용하도록 여는 상태입니다. UP이라고 케이블 연결이나 원격 통신 성공까지 보장되지는 않습니다.

net_device_ops

공통 네트워크 계층이 하드웨어 드라이버의 ndo_open·ndo_start_xmit 등을 호출하기 위한 함수 표입니다.

RTNL

인터페이스 구성 변경을 서로 조정하는 네트워크 잠금입니다. 이 내부 함수는 호출자가 적절한 잠금 조건을 마련했음을 요구합니다.

netpoll

특수 상황에서 네트워크를 폴링하는 경로입니다. 장치를 여는 도중 불완전한 상태의 드라이버를 건드리지 않도록 조정합니다.

처음 읽을 때

등록, 관리상 UP, 실제 링크 연결을 세 가지로 나누어 보십시오. __dev_open의 성공은 주로 장치를 사용하도록 준비한 상태를 뜻하며 패킷이 상대방까지 도착했다는 뜻은 아닙니다.

더 깊이 살펴볼 때

드라이버 콜백의 잠금 조건과 NETDEV_PRE_UP 알림 실패, ndo_open 실패를 구분하십시오. 이 함수의 각 오류 반환이 어떤 상태 변경 이전 또는 이후에 발생하는지 보면 상위 호출자의 책임도 드러납니다.

열기 결과를 가르는 분기 그림

__dev_open()은 장치가 계속 없거나 NETDEV_PRE_UP 통지가 오류를 돌려주면 START 설정 전에 끝납니다. 그 뒤 선택적인 주소 검증 또는 ndo_open이 실패하면 netpoll_poll_enable()을 거쳐 START를 지우고 오류를 반환합니다. ret == 0일 때만 UP 상태 설정과 dev_activate()로 진행합니다.

장치 부재와 PRE_UP 오류는 START 전 반환, 주소 검증과 ndo_open 오류는 START 해제 후 반환, 성공만 버전별 UP 설정과 dev_activate로 이어지는 다섯 경로
그림 1. Linux v6.18.37 __dev_open(). 그림 크게 보기. v6.6은 dev->flags |= IFF_UP, v6.18.37은 netif_set_up(dev, true)를 호출합니다. v6.18.37의 netdev_ops_assert_locked(dev)는 START 설정 뒤 추가되며 그림의 반환 경로를 바꾸지 않습니다.
분기별 반환과 START 처리
조건반환이 함수의 START·UP 처리
resume 후에도 장치 부재-ENODEVSTART 설정 전
NETDEV_PRE_UP 오류변환한 retSTART 설정 전
주소 검증 실패검증 retndo_open 생략, START 해제
ndo_open 실패open retSTART 해제
성공0버전별 UP 설정, dev_activate()

__dev_open를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

static int __dev_open(struct net_device *dev, struct netlink_ext_ack *extack)
{
	const struct net_device_ops *ops = dev->netdev_ops;
	int ret;

	ASSERT_RTNL();
	dev_addr_check(dev);

	if (!netif_device_present(dev)) {
		/* may be detached because parent is runtime-suspended */
		if (dev->dev.parent)
			pm_runtime_resume(dev->dev.parent);
		if (!netif_device_present(dev))
			return -ENODEV;
	}

	/* Block netpoll from trying to do any rx path servicing.
	 * If we don't do this there is a chance ndo_poll_controller
	 * or ndo_poll may be running while we open the device
	 */
	netpoll_poll_disable(dev);

	ret = call_netdevice_notifiers_extack(NETDEV_PRE_UP, dev, extack);
	ret = notifier_to_errno(ret);
	if (ret)
		return ret;

	set_bit(__LINK_STATE_START, &dev->state);

	netdev_ops_assert_locked(dev);

	if (ops->ndo_validate_addr)
		ret = ops->ndo_validate_addr(dev);

	if (!ret && ops->ndo_open)
		ret = ops->ndo_open(dev);

	netpoll_poll_enable(dev);

	if (ret)
		clear_bit(__LINK_STATE_START, &dev->state);
	else {
		netif_set_up(dev, true);
		dev_set_rx_mode(dev);
		dev_activate(dev);
		add_device_randomness(dev->dev_addr, dev->addr_len);
	}

	return ret;
}
static int __dev_open(struct net_device *dev, struct netlink_ext_ack *extack)

공통 네트워크 장치를 여는 내부 함수입니다. extack은 자세한 오류 설명을 전달하는 용도로 쓰입니다.

	const struct net_device_ops *ops = dev->netdev_ops;

장치별 드라이버 연산 표를 얻습니다.

	int ret;

사전 알림과 드라이버 콜백의 결과를 저장합니다.

	ASSERT_RTNL();

필요한 RTNL 보호 아래 호출됐는지 검증합니다. 이 줄이 잠금을 새로 잡는 것은 아닙니다.

	dev_addr_check(dev);

장치 주소 관리 상태를 점검합니다.

	if (!netif_device_present(dev)) {

장치가 현재 사용 가능한 상태로 연결되어 있는지 확인합니다.

		if (dev->dev.parent)

장치를 깨울 수 있는 부모 객체가 있는지 확인합니다.

			pm_runtime_resume(dev->dev.parent);

부모의 runtime PM 정지가 원인이었을 가능성을 고려해 재개를 요청합니다.

		if (!netif_device_present(dev))

부모 재개 후에도 장치가 사용 불가능한지 다시 확인합니다.

			return -ENODEV;

사용할 장치가 없다고 -ENODEV를 반환합니다.

	netpoll_poll_disable(dev);

장치를 여는 동안 netpoll이 불완전한 드라이버 상태를 사용하지 못하도록 조정합니다.

	ret = call_netdevice_notifiers_extack(NETDEV_PRE_UP, dev, extack);

NETDEV_PRE_UP 사전 알림을 보내 관련 기능이 장치 열기를 검사하게 합니다.

	ret = notifier_to_errno(ret);

notifier 형식 반환값을 일반 음수 오류 형식으로 바꿉니다.

	if (ret)

사전 알림에서 거부됐는지 검사합니다.

		return ret;

드라이버를 열지 않고 해당 오류를 반환합니다. 이 조기 반환과 아래 정상 정리 경로를 구분해 읽어야 합니다.

	set_bit(__LINK_STATE_START, &dev->state);

장치를 시작하는 상태 비트를 켭니다. 이 비트만으로 open 성공이 확정되는 것은 아닙니다.

	netdev_ops_assert_locked(dev);

드라이버 연산 호출에 필요한 잠금 상태를 검증합니다.

	if (ops->ndo_validate_addr)

드라이버가 주소 검증 함수를 제공하는지 확인합니다.

		ret = ops->ndo_validate_addr(dev);

MAC 주소 등 장치 주소가 허용되는지 드라이버에 묻습니다.

	if (!ret && ops->ndo_open)

앞 검사에 성공했고 드라이버 open 함수도 있을 때 실행합니다.

		ret = ops->ndo_open(dev);

드라이버에 장치를 사용할 준비를 하도록 요청합니다. 구체적 DMA·인터럽트 설정은 해당 드라이버 구현에 있습니다.

	netpoll_poll_enable(dev);

정상 진행 경로에서 netpoll 폴링을 다시 허용합니다.

	if (ret)

주소 검사 또는 드라이버 open에 실패했는지 검사합니다.

		clear_bit(__LINK_STATE_START, &dev->state);

실패했으므로 앞서 켠 시작 상태 비트를 되돌립니다.

	else {

장치 열기에 성공한 경우의 후처리입니다.

		netif_set_up(dev, true);

공통 장치 상태를 UP으로 설정합니다.

		dev_set_rx_mode(dev);

멀티캐스트 등 현재 설정에 맞는 수신 모드를 적용합니다.

		dev_activate(dev);

장치의 패킷 처리 큐를 활성화합니다.

		add_device_randomness(dev->dev_addr, dev->addr_len);

장치 주소 정보를 난수 초기화 재료에 더합니다. 주소 자체가 강한 비밀값이라는 뜻은 아닙니다.

	return ret;

열기 성공 또는 실패 코드를 반환합니다.

함께 생각해 볼 질문

ip link에서 보이면 하드웨어가 이미 켜졌나요?

등록된 상태일 수 있습니다. 실제 사용을 위한 open과 전원·큐 활성화는 별개입니다.

UP이면 인터넷 연결도 된 것인가요?

아닙니다. 링크, 주소, 라우팅, 상대방 상태 등 추가 조건이 필요합니다.

부모 장치를 깨우는 이유는 무엇인가요?

버스나 상위 장치가 정지돼 있으면 자식 NIC의 레지스터와 기능에 접근할 수 없는 경우가 있기 때문입니다.

출처와 읽은 범위

Linux stable v6.18.37 · net/core/dev.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑