Socket与系统调用深度分析
一、socketAPI与系统调用
基于socket编程,基本上就是以下6个步骤:1、socket()函数
2、bind()函数
3、listen()、connect()函数
4、accept()函数
5、read()、write()函数等
6、close()函数 这些函数使用系统调用通过软中断的方式去执行相应的内核处理函数。在linux中,与socket有关的系统调用有
1 2 3 4 5 6 7 8 9 |
__sys_socket
__sys_bind
__sys_listen
__sys_accept4
__sys_recvfrom
__sys_recvmsg
__sys_sendto
__sys_sendmsg
__sys_connect
|
二、内核处理函数
以__sys_bind为例,查看其源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
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处分别设置断点。