Linux网络编程之socket创建

对于用户态编程的第一个函数是

fd=socket(AF_INET,SOCK_STREAM,0);

该函数会通过系统调用进入内核,内核的实现如下:

kernel\net\Socket.c

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
	int retval;
	struct socket *sock;
	int flags;
    ...
    基本类型:由低8位设置共8种类型
    特殊类型:由其他bit位设置
    下面2句的意思就是判断socket创建的类型只能是基本类型(SOCK_STREAM,SOCK_DGRAM等)或者(SOCK_CLOEXEC,SOCK_NONBLOCK))如果还包含其他的高位地址的类型,则返回错误的参数
	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;
  
    对于类似alpha或者mips架构有可能会将SOCK_NONBLOCK设置为不等于O_NONBLOCK,因此针对我们的ARM架构可以忽略这条
	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    下面就是创建一个socket文件,注意这里的sock指针作为传入参数在sock_create完成赋值
	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;
    通过nlmsg_multicast将socket创建的消息发送出去
	if (retval == 0)
		sockev_notify(SOCKEV_SOCKET, sock);
    将这个新建的socket文件关联到一个文件中这个文件的fops为socket_file_ops,并返回这个关联的文件描述符

	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
	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;
}

sock_create

 ->__sock_create(current->nsproxy->net_ns,...)          这里的第一个参数代表的是当前进程的网络命名空间

struct socket *sock;
const struct net_proto_family *pf;
...
sock = sock_alloc();
sock->type = type;    ----SOCK_STREAM/SOCK_DGRAM
//针对我们当前的sock而言,socket的family是AF_INET,同时AF_INET是PF_INET的别称,因此返回的pf就是inet_family_ops
pf = rcu_dereference(net_families[family]);
...
//调用的自然就是inet_create
err = pf->create(net, sock, protocol, kern);//针对当前的调用情况,这个值kern目前为0
*res = sock;  //返回新建的sock文件,这里的*res就是sock_create传入的最后一个参数

这里要说明的是net_families这是一个指针数组定义如下:

static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

数组中的每一个元素都是一个指向结构体net_proto_family的指针,那么问题来了这些指针是怎么被放进去的呢,在sock.c文件中提供了一个函数sock_register用于填充这个数组。如下:

kernel\net\ipv4\Af_inet.c

static const struct net_proto_family inet_family_ops = {
	.family = PF_INET,
	.create = inet_create,
	.owner	= THIS_MODULE,
};

inet_init

 (void)sock_register(&inet_family_ops);
因此针对socket的创建最终就回来到inet_create这个函数

inet_create

这里inet_ehash_secret是一个4字节的整型数代表的是一个加密字串,如果这个字串还没有设置,同时报文既不是SOCK_RAW也不是SOCK_DGRAM,那么我们先设置加密字串,回到我们应用程序中,我们设置的类型是SOCK_STREAM,因此这里是需要设置这个加密字串的,但是只会设置一次
if (unlikely(!inet_ehash_secret))
 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
   build_ehash_secret();
设置当前socket状态为未连接
sock->state = SS_UNCONNECTED;
lookup_protocol:
这里的inetsw是一个静态链表类型的数组,保存的是像tcp或者udp协议对应的链表元素,因此下面这句的意思就是通过sock->type,具体到我当前的应用而言就是SOCK_STREAM,通过这个下标找到tcp对应的inet_protosw类型的answer指针
list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
 ....
}
更新相关字段,这里面sock->ops这个字段很关键
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
创建sock文件,这里的sk是struct sock *sk;
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
sk->sk_no_check = answer_no_check;
因为inet内嵌的第一个元素就是sock结构体,因此这里将sk强制转换为inet
inet = inet_sk(sk);
对于TCP中inet->is_icsk这个字段为true,其他协议为false
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
下面是是否使能MTU的路径发现机制,可以通过"/proc/sys/net/ipv4/ip_no_pmtu_disc"去设置
所谓的MTU的路径发现机制:
 我们知道如果应用层的数据报文过大,IP会分片,如果使能了MTU路径发现机制,那么应用层会逐步增大报文的长度尝试向对端发送报文,一旦路径中的主机发现报文超过了它设置的MTU值,就会丢弃报文,同时向报文的源端发送“报文过大”的ICMP消息。从而源端就可以知道在不分片的情况下链路最大可以设置的MTU
if (ipv4_config.no_pmtu_disc)
    inet->pmtudisc = IP_PMTUDISC_DONT;
else
 inet->pmtudisc = IP_PMTUDISC_WANT; 
inet->inet_id = 0;
利用struct socket进一步初始化struct sock结构体,这里也会设置sock的发送和接收的缓冲区的大小
sock_init_data(sock, sk);
...
if (sk->sk_prot->init) {
  err = sk->sk_prot->init(sk);
  if (err)
   sk_common_release(sk);
 }
这里的sk->sk_prot->init对应是TCP协议的tcp_prot中的init函数tcp_v4_init_sock


这里详细说明下inetsw中的元素通过inet_register_protosw的方式将下面协议逐个加进去的

static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
		.no_check =   0,
		.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
	},

	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		.prot =       &udp_prot,
		.ops =        &inet_dgram_ops,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_PERMANENT,
       },

       {
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_ICMP,
		.prot =       &ping_prot,
		.ops =        &inet_dgram_ops,
		.no_check =   UDP_CSUM_DEFAULT,
		.flags =      INET_PROTOSW_REUSE,
       },

       {
	       .type =       SOCK_RAW,
	       .protocol =   IPPROTO_IP,	/* wild card */
	       .prot =       &raw_prot,
	       .ops =        &inet_sockraw_ops,
	       .no_check =   UDP_CSUM_DEFAULT,
	       .flags =      INET_PROTOSW_REUSE,
       }
};


再说下最后一个函数,之前我们有提到的tcp_v4_init_sock

kernel\net\ipv4\Tcp_ipv4.c

static int tcp_v4_init_sock(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	tcp_init_sock(sk);

	icsk->icsk_af_ops = &ipv4_specific;

#ifdef CONFIG_TCP_MD5SIG
	tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
#endif

	return 0;
}

kernel\net\ipv4\Tcp.c

void tcp_init_sock(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);

	skb_queue_head_init(&tp->out_of_order_queue);
	tcp_init_xmit_timers(sk);
	tcp_prequeue_init(tp);
	INIT_LIST_HEAD(&tp->tsq_node);

	icsk->icsk_rto = TCP_TIMEOUT_INIT;
	tp->mdev = TCP_TIMEOUT_INIT;

	/* So many TCP implementations out there (incorrectly) count the
	 * initial SYN frame in their delayed-ACK and congestion control
	 * algorithms that we must have the following bandaid to talk
	 * efficiently to them.  -DaveM
	 */
	tp->snd_cwnd = TCP_INIT_CWND;

	/* See draft-stevens-tcpca-spec-01 for discussion of the
	 * initialization of these values.
	 */
	tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
	tp->snd_cwnd_clamp = ~0;
	tp->mss_cache = TCP_MSS_DEFAULT;

	tp->reordering = sysctl_tcp_reordering;
	tcp_enable_early_retrans(tp);
	icsk->icsk_ca_ops = &tcp_init_congestion_ops;

	tp->tsoffset = 0;

	sk->sk_state = TCP_CLOSE;

	sk->sk_write_space = sk_stream_write_space;
	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);

	icsk->icsk_sync_mss = tcp_sync_mss;

	/* Presumed zeroed, in order of appearance:
	 *	cookie_in_always, cookie_out_never,
	 *	s_data_constant, s_data_in, s_data_out
	 */
     这里会再次更新发送和接收缓冲区的大小,这个值可以通过下面的方式查看和修改:
  "/sys/kernel/ipv4/tcp_wmem_def"
	sk->sk_sndbuf = sysctl_tcp_wmem[1];
	sk->sk_rcvbuf = sysctl_tcp_rmem[1];

	local_bh_disable();
	sock_update_memcg(sk);
	sk_sockets_allocated_inc(sk);
	local_bh_enable();
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值