Network · Linux v6.6 · source + diagram note

socket

socket()의 fd 생성부터 프로토콜 연산·닫기 경계까지.

전체 흐름

이 글은 socket() 호출이 파일 디스크립터를 만들고, struct socket을 프로토콜 연산에 연결하는 경계를 다룬다. NIC·NAPI·qdisc는 이후 패킷 경로의 다른 층이며 소켓 생성의 직접 단계가 아니다.

그림 1. 사용자 소켓의 생성·사용·닫기 경계
  1. socket(family,type,protocol)사용자가 주소 계열·종류·프로토콜을 요청한다.
  2. __sys_socket_createtype 플래그를 검사하고 sock_create()를 호출한다.
  3. __sock_create → pf->create주소 계열의 생성 연산이 socket을 준비한다. sk 연결은 계열 구현에 따른다.
  4. sock_map_fdsock_alloc_file()이 file을 연결하고 fd_install()이 fd를 노출한다.
  5. sendmsg / recvmsg공통 소켓 층이 보안 검사 뒤 sock->ops 콜백으로 전달한다.
  6. close → file release마지막 file 참조 정리 시 sock_close()가 프로토콜 release에 연결된다.

분기: 유효하지 않은 family/type, 보안 검사 실패, 계열 생성 실패, fd/file 할당 실패에는 정상 fd가 생기지 않는다. bind·connect는 별도 호출이며 생성 함수 자체가 연결 상태를 만들지는 않는다.

소스 코드 위치

net/socket.c가 시스템 호출, fd/file 연결, send/recv 공통 디스패치, close 경계를 포함한다. include/linux/net.h는 struct socket과 proto_ops를 정의한다. net/ipv4/af_inet.c는 AF_INET 계열의 구현 예시이며 모든 주소 계열의 공통 구현은 아니다.

Linux v6.6 원본: net/socket.c include/linux/net.h include/net/sock.h net/ipv4/af_inet.c

위치읽을 경계
__sys_socket프로토콜 선택 후 __sys_socket_create → sock_map_fd.
__sock_createfamily/type 검사, 보안 검사, pf->create, 실패 시 정리.
sock_sendmsg / sock_recvmsg보안 검사와 sock->ops 콜백. 버퍼링·대기 조건은 선택된 프로토콜 구현에서 확인한다.
socket_file_ops.releasesock_close → __sock_release → 프로토콜 release.

대표 코드

__sys_socket_create / __sys_socket_file 발췌

원본 코드: net/socket.c, 두 생성 보조 함수가 포함된 연속 발췌
{
	return __sock_create(net, family, type, protocol, res, 1);
}
EXPORT_SYMBOL(sock_create_kern);

static struct socket *__sys_socket_create(int family, int type, int protocol)
{
	struct socket *sock;
	int retval;

	/* Check the SOCK_* constants for consistency.  */
	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

	if ((type & ~SOCK_TYPE_MASK) & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return ERR_PTR(-EINVAL);
	type &= SOCK_TYPE_MASK;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		return ERR_PTR(retval);

	return sock;
}

struct file *__sys_socket_file(int family, int type, int protocol)
{
	struct socket *sock;
	int flags;

	sock = __sys_socket_create(family, type, protocol);
	if (IS_ERR(sock))
		return ERR_CAST(sock);

	flags = type & ~SOCK_TYPE_MASK;
	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

읽는 법: 시작 중괄호와 sock_create_kern 끝부분은 앞 함수의 꼬리다. 이어지는 본문은 type 비트 검사와 sock_create() 호출을 보여 주지만, 실제 사용자 fd를 설치하는 __sys_socket() 전체는 이 발췌 밖에 있다.

sock_create_lite 발췌

원본 코드: net/socket.c, sock_create_lite() 주석과 함수 앞부분
		}
	return err;
}

/**
 *	sock_create_lite - creates a socket
 *	@family: protocol family (AF_INET, ...)
 *	@type: communication type (SOCK_STREAM, ...)
 *	@protocol: protocol (0, ...)
 *	@res: new socket
 *
 *	Creates a new socket and assigns it to @res, passing through LSM.
 *	The new socket initialization is not complete, see kernel_accept().
 *	Returns 0 or an error. On failure @res is set to %NULL.
 *	This function internally uses GFP_KERNEL.
 */

int sock_create_lite(int family, int type, int protocol, struct socket **res)
{
	int err;
	struct socket *sock = NULL;

	err = security_socket_create(family, type, protocol, 1);
	if (err)
		goto out;

	sock = sock_alloc();
	if (!sock) {
		err = -ENOMEM;
		goto out;
	}

	sock->type = type;
	err = security_socket_post_create(sock, family, type, protocol, 1);
	if (err)
		goto out_release;

out:
	*res = sock;
	return err;

읽는 법: 이 함수는 일반 socket() 생성 경로의 sock_create()와 다르다. 주석대로 초기화가 아직 완전하지 않은 가벼운 socket을 준비하며, 수락 경로 등에서 사용된다. 마지막 out_release 정리는 발췌 밖에 있다.

함수별 분석

기존 코드 블록의 제목을 바로잡았다. 일반 사용자 socket() 경로는 아래 순서이며, 기존 두 번째 발췌인 sock_create_lite()를 이 직선 경로에 끼워 넣지 않는다.

단계함수결과
입력__sys_sockettype의 별도 플래그를 해석하고 프로토콜 선택 훅을 거친다.
객체 생성__sys_socket_create → sock_create → __sock_create주소 계열의 pf->create가 socket용 구현을 설정한다. 이 시점에는 사용자 fd가 없다.
파일 연결sock_map_fd → sock_alloc_filefile->private_data가 socket을 가리키고 성공 시 fd_install한다.
이후 I/Osock_sendmsg / sock_recvmsg공통 층은 보안 검사를 하고 프로토콜 연산으로 전달한다.
정리sock_close → __sock_releasesock->ops->release 후 연결 상태를 정리한다.

구조체와 필드

그림 2. fd에서 프로토콜 상태까지의 객체 경계
사용자 fd태스크의 fd 테이블이 struct file을 참조한다.
struct fileprivate_data → struct socket; socket_file_ops가 파일 연산을 연결한다.
struct socketops → proto_ops, sk → 프로토콜 상태가 있을 수 있다.
프로토콜 구현sendmsg, recvmsg, release 동작과 대기·버퍼 의미를 정한다.

fd, file, socket, sock는 같은 객체가 아니다. fd가 복제되면 한 fd의 close()가 곧바로 공통 file의 마지막 참조 해제를 뜻하지 않는다.

대상핵심 연결혼동 방지
fdfd_install로 file에 매핑정수 번호 자체가 protocol socket은 아니다.
struct fileprivate_data가 socket을 가리킴fd 공유·복제와 file 수명을 구분한다.
struct socketops, sk, file, wqsk 연결은 계열별 구현과 단계에 좌우된다.
proto_ops / struct sock연산 콜백 / 프로토콜 상태TCP 큐 동작을 모든 socket의 공통 속성으로 일반화하지 않는다.

실행 단계와 실패 분기

오류가 나면 fd 생성 완료처럼 그릴 수 없다. 반환 코드와 socket/file의 정리 위치를 분리한다.

조건코드 위치결과
type 플래그 오류__sys_socket_create-EINVAL 오류 포인터; sock_create 호출 전.
family 미등록·보안 검사 실패__sock_create오류 반환과 필요한 socket/module 참조 정리; fd는 없다.
pf->create 실패__sock_create프로토콜 구현의 부분 생성 정리와 공통 에러 경로를 함께 확인한다.
fd 할당 실패sock_map_fdsock_release() 후 오류 반환.
file 할당 실패sock_alloc_file / sock_map_fdsocket 정리 및 예약한 fd 반환; 설치된 fd는 없다.
file 최종 정리socket_file_ops.releasesock_close에서 프로토콜 release 호출.

v6.6의 __sock_create는 pf->create 실패 시 out_module_put으로 이동한다. v6.18.37에는 실패 뒤 sock->sk 잔존 여부를 확인하는 경고가 추가됐다.

불변 조건과 실패 사례

공개 시점

사용자 fd는 socket 생성과 file 연결이 성공한 뒤 fd_install에서 노출된다. 그 전의 실패는 설치되지 않은 객체를 정리한다.

객체 수명

fd와 file 참조, struct socket, 프로토콜의 struct sock 수명을 같은 하나로 보지 않는다. 공유된 file의 마지막 정리와 protocol release의 경계를 확인한다.

I/O 경계

공통 sock_sendmsg/sock_recvmsg는 보안 검사를 거쳐 proto_ops로 보낸다. 대기·수신 큐·송신 버퍼는 프로토콜별 구현을 따로 읽어야 한다.

상태 변경

생성 자체가 bind나 connect를 수행한다고 해석하지 않는다. 해당 연산은 별도 API와 sock->ops 콜백이다.

계측과 검증

tracepoint의 존재는 빌드 설정에 따라 다르다. syscall 결과, socket 층 send/recv 길이, 프로토콜 상태는 서로 다른 관찰 지점이다.

관찰 지점알 수 있는 것한계
syscalls:sys_enter_socket / exit사용자 호출 인자와 반환 fd 또는 오류프로토콜 큐 상태나 이후 I/O까지 보여 주지 않는다.
sock_send_length / sock_recv_length활성화된 빌드에서 socket 층의 반환 길이·오류버퍼·NIC·NAPI의 세부 원인을 단독으로 판정하지 못한다.
ss -tinp지원하는 TCP 소켓 상태의 예시모든 socket family의 공통 진단은 아니다.
tracefs에서 실제 이벤트 확인:
ls /sys/kernel/tracing/events/syscalls/*socket*
ls /sys/kernel/tracing/events/sock/

추가 확인 사항

  • __sys_socket_create() 성공과 사용자 fd 설치는 어느 함수에서 갈라지는가?
  • sock_create_lite()를 일반 socket() 생성 경로로 그리면 어떤 초기화 단계가 빠지는가?
  • fd를 복제한 뒤 하나만 닫았을 때 socket_file_ops.release의 실행 시점을 어떻게 확인할 것인가?
  • send/recv가 프로토콜 콜백으로 넘어간 뒤 버퍼링과 대기 조건은 어느 구현에서 확인해야 하는가?