컴퓨터공부

네트워크 디바이스 드라이버 - 소켓의 생성

achivenKakao 2006. 9. 4. 18:46

Socket 생성

소켓의 생성과 관련된 된 시스템 콜은 sys_socket()이다. 시스템 콜을 프로그램에서 하는 방법은 아래와 같다.

#include <sys/types.h>

#include <sys/socket.h>

int socket( int domain, int type, int protocol );

넘겨주는 값은 domain에 해당하는 것으로는 PF_XXX로 시작하는 값을 가지며, 프로토콜의 패밀리를 나타낸다. 정의는 ~/include/linux/socket.h에 있다. Type에는 SOCK_XXX로 시작하는 값을 가지며, ~/include/asm/socket.h에 정의되어 있다. Type은 소켓의 타입을 결정한다. 마지막으로 protocol 필드에는 RAW socket을 제외하고는 항상 0을 가지도록 한다.

asmlinkage long sys_socket(int family, int type, int protocol)

{

             int retval;

             struct socket *sock;

             retval = sock_create(family, type, protocol, &sock);

             if (retval < 0)

                           goto out;

             retval = sock_map_fd(sock);

             if (retval < 0)

                           goto out_release;

out:

             /* It may be already another descriptor 8) Not kernel problem. */

             return retval;

out_release:

             sock_release(sock);

             return retval;

}

코드 1. sys_socket() 함수

sys_socket함수는 하나의 BSD 소켓 구조체[1]를 생성해서(sock_create()), 이것을 파일 디스크립터의 하나로 맵핑시킨다(sock_map_fd()). 수행 도중에 에러가 발생했다면, 이전에 할당 받은 BSD 소켓 구조체가 있다면 이를 해제하고 복귀한다. 에러가 없다면 매핑된 파일 디스크립터를 돌려줄 것이다.

int sock_create(int family, int type, int protocol, struct socket **res)

{

             int i;

             struct socket *sock;

             if(family<0 || family>=NPROTO)

                           return -EAFNOSUPPORT;

             if (family == PF_INET && type == SOCK_PACKET) {

                           static int warned;

                           if (!warned) {

                                        warned = 1;

                                        printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm);

                           }

                           family = PF_PACKET;

             }

코드 2. sock_create()함수 (1)

sock_create()함수는 BSD 소켓을 하나 생성하는 역할을 한다. 먼저 프로토콜 패밀리가 정해진 범위에 있는지를 확인하고, 만약 그렇지 않다면 ?EAFNOSUPPORT를 돌려준다. 만약 PF_INET을 프로토콜 패밀리로 가지고, 원하는 소켓의 타입이 SOCK_PACKET이라면, 호환성을 고려해서 프로토콜 패밀리를 PF_PACKET으로 바꾼다.

#if defined(CONFIG_KMOD) && defined(CONFIG_NET)

             if (net_families[family]==NULL)

             {

                           char module_name[30];

                           sprintf(module_name,"net-pf-%d",family);

                           request_module(module_name);

             }

#endif

             net_family_read_lock();

             if (net_families[family] == NULL) {

                           i = -EAFNOSUPPORT;

                           goto out;

             }

코드 3. sock_create() 함수 (2)

이젠 해당하는 프로토콜 패밀리를 커널에서 지원하는 지를 확인하는 일이다. 만약 커널에서 이미 해당 프로토콜 패밀리를 가지고 있지 않다면, 모듈의 이름으로 net-pf-XXX를 가지는 프로토콜 패밀리의 모듈을 적재하도록 요청한다(request_module()). 그리고 나서, 네트워크 패밀리에 대한 읽기 lock을 설정한 후, 해당 프로토콜 패밀리 모듈이 다시 적재되었는지 확인한다. 이때, 프로토콜 패밀리에 NULL을 가진다면 ?EAFNOSUPPORT를 에러로 돌려준다.

             if (!(sock = sock_alloc()))

             {

                           printk(KERN_WARNING "socket: no more sockets\n");

                           i = -ENFILE;                      /* Not exactly a match, but its the

                                                                     closest posix thing */

                           goto out;

             }

             sock->type  = type;

             if ((i = net_families[family]->create(sock, protocol)) < 0)

             {

                           sock_release(sock);

                           goto out;

             }

             *res = sock;

out:

             net_family_read_unlock();

             return i;

}

코드 4. sock_create() 함수 (3)

마지막으로 남은 것은 실제적인 BSD 소켓 구조체의 할당이다. 이것은 sock_alloc() 함수가 처리한다. sock_alloc()함수가 0을 돌려준다면 더 이상 BSD 소켓을 생성할 수 없다는 말이되며 out으로 제어를 옮긴다. 제대로 할당 받았다면, 이젠 BSD 소켓 구조체의 type 필드에 넘겨받은 type을 넣고, 프로토콜 패밀리에서 제공하는 create() 함수를 호출한다. 넘겨주는 것은 할당받은 BSD 소켓 구조체와 넘겨받은 protocol이다. 만약 에러가 있다면 0보다 작은 값을 넘겨받을 것이며, 할당받은 BSD 소켓 구조체를 해제하고(sock_release()) out으로 제어를 옮긴다. 최종 결과는 생성된 BSD 소켓 구조체이며, 복귀전에 설정한 lock을 해제하고 에러코드와 함께 돌아간다.

struct socket *sock_alloc(void)

{

             struct inode * inode;

             struct socket * sock;

             inode = get_empty_inode();

             if (!inode)

                           return NULL;

             inode->i_sb = sock_mnt->mnt_sb;

             sock = socki_lookup(inode);

             inode->i_mode = S_IFSOCK|S_IRWXUGO;

             inode->i_sock = 1;

             inode->i_uid = current->fsuid;

             inode->i_gid = current->fsgid;

             sock->inode = inode;

             init_waitqueue_head(&sock->wait);

             sock->fasync_list = NULL;

             sock->state = SS_UNCONNECTED;

             sock->flags = 0;

             sock->ops = NULL;

             sock->sk = NULL;

             sock->file = NULL;

             sockets_in_use[smp_processor_id()].counter++;

             return sock;

}

코드 5. sock_alloc() 함수

sock_alloc()함수는 inode object BSD 소켓을 연결하는 역할을 한다. , inode object를 하나 찾아와서(get_empty_inode()), i_sb필드에 sock_mnt->mnt_sb를 연결하고, inode에 관련된 BSD 소켓을 찾은 후(socki_lookup()), inode의 각 필드들을 적절한 값으로 설정한다. 그리고, BSD 소켓의 inode필드를 이 inode를 연결하는데 사용하고, wait큐를 초기화한다(init_waitqueue_head()). 나머지는 BSD 소켓에 대한 초기화이다. 아직 연결되지 않은 상태이므로 SS_UNCONNECTED가 상태로 설정된다. BSD 소켓에 대한 사용 카운터를, CPU ID를 인덱스 값으로 해서 찾아서 증가 시켜준다. 돌려주는 값은 찾은 BSD 소켓이 된다.

void sock_release(struct socket *sock)

{

             if (sock->ops)

                           sock->ops->release(sock);

             if (sock->fasync_list)

                           printk(KERN_ERR "sock_release: fasync list not empty!\n");

             sockets_in_use[smp_processor_id()].counter--;

             if (!sock->file) {

                           iput(sock->inode);

                           return;

             }

             sock->file=NULL;

}

코드 6. sock_release() 함수

sock_release()함수는 단순히 이전에 할당받은 inode를 해제하기만 하면 된다. 먼저 이것을 해주기 전에 BSD 소켓 연산 구조체의 release()함수를 호출해서 해제되기 위한 처리를 한 다음, 소켓의 fasync_list에 멤버가 있는지 확인한다. 멤버가 있다면, 에러 상황이다.

CPU ID를 인덱스로 한 BSD 소켓 사용 카운터를 감소시킨 후, 만약 소켓의 file 필드가 NULL을 가진다면 inode object를 버리고(iput()) 복귀한다. 그렇지 않다면, 일단 file 필드에 NULL을 넣는다. 나중에 다시 한번 호출이 있을 경우가 되면, 이미 file 필드가 NULL로 되어 있으므로 inode object가 지워질 것이다.

참고로 inode object를 보면 union으로 정의된 곳에 BSD 소켓을 위한 부분을 찾을 수 있을 것이다. 따라서, inode object만을 할당하더라도 BSD 소켓 구조체가 할당된다.

이제 남은 것은 프로토콜 패밀리의 create함수이다. 우리가 볼 것은 INET에 대한 것이기에 프로토콜 패밀리도 PF_INET이 해당한다. 아래와 같다.

net_families[] net_proto_family구조체[2]의 배열로 정의되며, 프로토콜 패밀리의 이름과 생성 함수(create()), 인증과 암호화에 대한 필드로 구성된다. 정의는 ~/net/socket.c를 참조하기 바란다. 새로운 프로토콜 패밀리의 등록은 sock_register()가 맡고 있으며, 해제는 sock_unregister() 함수[3]가 담당한다. PF_INET 프로토콜 패밀리에 대한 초기화는 ~/net/ipv4/af_inet.c에 있는 inet_init()함수가 맡고 있다. 이곳에서 sock_register()함수를 호출하는 것으로 하나의 프로토콜 패밀리를 등록한다. 넘겨 주는 값은 inet_family_ops의 주소이며, 정의는 ~/net/ipv4/af_inet.c에 아래와 같이 나온다.

struct net_proto_family inet_family_ops = {

             PF_INET,

             inet_create

};

코드 7. inet_family_ops 정의

, PF_INET으로 프로토콜 패밀리 이름을 등록하고, inet_create() 함수를 프로토콜 패밀리의 create()함수로 서절했다. inet_create() 함수는 아래와 같다.

static int inet_create(struct socket *sock, int protocol)

{

             struct sock *sk;

             struct proto *prot;

             sock->state = SS_UNCONNECTED;

             sk = sk_alloc(PF_INET, GFP_KERNEL, 1);

             if (sk == NULL)

                           goto do_oom;

코드 8. inet_create() 함수 (1)

inet_create()함수는 INET 프로토콜 레이어에서 사용할 INET 소켓[4]을 생성하는 일을 한다. INET 소켓의 구조체는 sock를 사용하며, 자세한 정의는 뒤에서 보도록 하겠다. 일단 현재 BSD 소켓은 연결 상태가 아니므로 SS_UNCONNECTED로 상태를 가진다. 그리고, 나서 해당 프로토콜 패밀리에 맞는 INET 소켓을 하나 할당 받는다(sk_alloc()). 넘겨주는 값은 PF_INET과 커널 메모리의 할당 우선 순위와 1을 준다. 만약 할당 받을 수 없다면 do_oom으로 제어를 옮긴다.

             switch (sock->type) {

             case SOCK_STREAM:

                           if (protocol && protocol != IPPROTO_TCP)

                                        goto free_and_noproto;

                           protocol = IPPROTO_TCP;

                           prot = &tcp_prot;

                           sock->ops = &inet_stream_ops;

                           break;

             case SOCK_SEQPACKET:

                           goto free_and_badtype;

             case SOCK_DGRAM:

                           if (protocol && protocol != IPPROTO_UDP)

                                        goto free_and_noproto;

                           protocol = IPPROTO_UDP;

                           sk->no_check = UDP_CSUM_DEFAULT;

                           prot=&udp_prot;

                           sock->ops = &inet_dgram_ops;

                           break;

             case SOCK_RAW:

                           if (!capable(CAP_NET_RAW))

                                        goto free_and_badperm;

                           if (!protocol)

                                        goto free_and_noproto;

                           prot = &raw_prot;

                           sk->reuse = 1;

                           sk->num = protocol;

                           sock->ops = &inet_dgram_ops;

                           if (protocol == IPPROTO_RAW)

                                        sk->protinfo.af_inet.hdrincl = 1;

                           break;

             default:

                           goto free_and_badtype;

             }

코드 9. inet_create() 함수 (2)

이젠 소켓의 타입에 맞는 것들로 할당받은 INET 소켓을 초기화 하는 일이다. SOCK_STREAM SOCK_SEQPACKET, SOCK_DGRAM, SOCK_RAW등의 소켓 타입이 있으며, 해당하지 않는다면 free_and_badtype으로 제어를 옮긴다. SOCK_STREAM TCP를 사용한 연결이다. protocol IPPROTO_TCP로 설정하고, prot tcp_prot의 주소로 만든 다음 INET 소켓의 연산 구조체로 inet_stream_ops를 설정한다. SOCK_SEQPACKET에 대해서는 지원하고 있지 않으므로 그냥 free_and_badtype으로 제어를 옮긴다. SOCK_DGRAM UDP를 사용하는 경우로 protocol IPPROTO_UDP로 설정하고, UDP에서 사용하는 default checksum을 사용한다는 것으로 INET 소켓의 no_check을 설정한 다음, prot udp_prot의 주소로 만든다. INET 소켓의 연산 구조체는 당연히 inet_dgram_ops가 될 것이다. SOCK_RAW RAW 소켓을 사용하는 경우로, 현재 capability CAP_NET_RAW 지원하는지 확인한다. 지원하지 않는다면 free_and_badperm으로 제어를 옮긴다. 만약 protocol 0값 가진다면 다시 제어를 free_and_noproto로 옮긴다. BSD 소켓의 reuse 필드를 1로 설정하고, num 필드를 protocol로 초기화 하며, 연산 구조체는 inet_dgram_ops로 둔다. , 데이터 그램으로 전송을 하겠다는 의미가 된다. 만약 protocol IPPROTO_RAW로 되어 있다면, INET 소켓의 protoinfo.af_inet.hdrincl 1로 설정한다.

             if (ipv4_config.no_pmtu_disc)

                           sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_DONT;

             else

                           sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_WANT;

             sock_init_data(sock,sk);

             sk->destruct = inet_sock_destruct;

             sk->zapped = 0;

             sk->family = PF_INET;

             sk->protocol = protocol;

             sk->prot = prot;

             sk->backlog_rcv = prot->backlog_rcv;

             sk->protinfo.af_inet.ttl=sysctl_ip_default_ttl;

             sk->protinfo.af_inet.mc_loop=1;

             sk->protinfo.af_inet.mc_ttl=1;

             sk->protinfo.af_inet.mc_index=0;

             sk->protinfo.af_inet.mc_list=NULL;

#ifdef INET_REFCNT_DEBUG

             atomic_inc(&inet_sock_nr);

#endif

코드 10. inet_create() 함수 (3)

이젠 할당받은 INET 소켓의 일반적인 초기화를 할 차례이다. IPv4의 현재 설정이 경로 최대 전송 단위(Path Maximum Transfer Unit)를 찾도록 설정되었다면, IP_PMTUDISC_WANT, 그렇지 않다면 IP_PMTUDISK_WANT INET 소켓의 protinfo.af_inet.pmtudisc에 넣어준다.

sock_init_data()함수는 ~/net/core/sock.c에 정의되어 있으며, INET 소켓의 필드들 및 함수들과 큐에 대해 기본적인 초기화를 한다. 그 이하의 부분들 역시 INET 소켓에 대한 초기화이다. inet_sock_destruct() 함수는 INET 소켓을 없애는 역할을 하는 함수로 ~/net/ipv4/af_inet.c에 정의되어 있다.

             if (sk->num) {

            &nbs