Socket与系统调用深度分析
一、socketAPI与系统调用
基于socket编程,基本上就是以下6个步骤:
1、socket()函数
2、bind()函数
3、listen()、connect()函数
4、accept()函数
5、read()、write()函数等
6、close()函数
这些函数使用系统调用通过软中断的方式去执行相应的内核处理函数。在linux中,与socket有关的系统调用有1、socket()函数
2、bind()函数
3、listen()、connect()函数
4、accept()函数
5、read()、write()函数等
6、close()函数
__sys_socket __sys_bind __sys_listen __sys_accept4 __sys_recvfrom __sys_recvmsg __sys_sendto __sys_sendmsg __sys_connect
二、内核处理函数
以__sys_bind为例,查看其源码
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) { struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { err = move_addr_to_kernel(umyaddr, addrlen, &address); if (!err) { err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen); if (!err) err = sock->ops->bind(sock, (struct sockaddr *) &address, addrlen); } fput_light(sock->file, fput_needed); } return err; }
从中可以看到__sys_bind将用户态的参数复制到内核空间,然后调用调用sock->ops->bind ,查看ops所指向的inet_bind函数
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; struct sock *sk = sock->sk; struct inet_sock *inet = inet_sk(sk); struct net *net = sock_net(sk); unsigned short snum; int chk_addr_ret; int err; if (sk->sk_prot->bind) { err = sk->sk_prot->bind(sk, uaddr, addr_len); // goto out; } err = -EINVAL; if (addr_len < sizeof(struct sockaddr_in)) goto out; if (addr->sin_family != AF_INET) { err = -EAFNOSUPPORT; if (addr->sin_family != AF_UNSPEC || addr->sin_addr.s_addr != htonl(INADDR_ANY)) goto out; } chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr); err = -EADDRNOTAVAIL; if (!sysctl_ip_nonlocal_bind && !(inet->freebind || inet->transparent) && addr->sin_addr.s_addr != htonl(INADDR_ANY) && chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST) goto out; snum = ntohs(addr->sin_port); err = -EACCES; if (snum && snum < PROT_SOCK && !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) goto out; lock_sock(sk); err = -EINVAL; if (sk->sk_state != TCP_CLOSE || inet->inet_num) goto out_release_sock; inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) inet->inet_saddr = 0; if (sk->sk_prot->get_port(sk, snum)) { inet->inet_saddr = inet->inet_rcv_saddr = 0; err = -EADDRINUSE; goto out_release_sock; } if (inet->inet_rcv_saddr) sk->sk_userlocks |= SOCK_BINDADDR_LOCK; if (snum) sk->sk_userlocks |= SOCK_BINDPORT_LOCK; inet->inet_sport = htons(inet->inet_num); inet->inet_daddr = 0; inet->inet_dport = 0; sk_dst_reset(sk); err = 0; out_release_sock: release_sock(sk); out: return err; }
这个函数主要功能是通过调用chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr)验证ip地址是单播、多播还是广播,调用sk->sk_prot->get_port(sk, snum)绑定端口。
三、用gdb追踪分析socket接口
使用gdbserver调试,__sys_listen ,___sys_bind,__sys_socket处分别设置断点。
运行replyhi,观察断点,实验完成。
可见在socket连接时确实调用了各种处理函数完成socket连接通信。