在分析三次握手的流程时,我们知道当收到ack或synack时都要调用下面这个函数:
- int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
- struct tcphdr *th, unsigned len)
- {
- ......
- switch (sk->sk_state) {
- case TCP_SYN_SENT://主机发送syn包后,状态为TCP_SYN_SENT;当我们收到synack时,就会调用
- tcp_rcv_synsent_state_process函数
- queued = tcp_rcv_synsent_state_process(sk, skb, th, len);//函数会调用
- tcp_init_congestion_control进行拥塞控制初始化
- ......
- }
- ......
- /* step 5: check the ACK field */
- if (th->ack) {//主机发送synack包后,收到的ack或者为第一个数据包的ack
- int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0;
- switch (sk->sk_state) {
- case TCP_SYN_RECV://主机发送synack后,状态变为TCP_SYN_RCV,至于为什么没有立即变为
- ESTABLISHED,以后再说
- ......
- tcp_init_congestion_control(sk);//设置拥塞控制机制
- ......
- break;
- ......
- } else
- goto discard;
- ......
- }
分析tcp_init_congestion_control函数:
- void tcp_init_congestion_control(struct sock *sk)
- {
- struct inet_connection_sock *icsk = inet_csk(sk);
- struct tcp_congestion_ops *ca;
- /* if no choice made yet assign the current value set as default */
- if (icsk->icsk_ca_ops == &tcp_init_congestion_ops) {//在tcp_v4_init_sock函数中,我们
- icsk->icsk_ca_ops = &tcp_init_congestion_ops
- rcu_read_lock();
- list_for_each_entry_rcu(ca, &tcp_cong_list, list) {//遍历查找第一个active的拥塞模
- 块,并设置为连接的拥塞控制
- if (try_module_get(ca->owner)) {//模块存在并且模块是live(不为
- MODULE_STATE_GOING)
- icsk->icsk_ca_ops = ca;
- break;
- }
- /* fallback to next available */
- }
- rcu_read_unlock();
- }
- if (icsk->icsk_ca_ops->init)
- icsk->icsk_ca_ops->init(sk);//拥塞控制钩子中的init函数非空,就调用init进行初始化;连接建立时才会调用这个函数,所以如果要在连接刚建立时进行操作,可以在init函数中添加你的代码,毕竟拥塞控制是模块插入的,不必修改内核
- }
内核是在tcp_cong_list中查找第一个active的拥塞控制模块并设置为当前连接的拥塞控制策略,在拥塞模块注册的时候是将模块插到链表的末尾;如果要使它起作用,必须设置
sysctl -w net.ipv4.tcp_congestion_control=(your congestion control ops name)
这个设置的作用是在你的拥塞控制放到链表的第一个节点位置上;早期版本的内核,如2.6.18,在注册时是将模块插到链表的头部,所以不用设置就会在下个连接建立时被设置。
在sysctl_net_ipv4.c中
- {
- .procname = "tcp_congestion_control",
- .mode = 0644,
- .maxlen = TCP_CA_NAME_MAX,
- .proc_handler = proc_tcp_congestion_control,//procfs提供的回调函数
- },
当我们通过sysctl -w操作时,就会调用回调函数:
- static int proc_tcp_congestion_control(ctl_table *ctl, int write,
- void __user *buffer, size_t *lenp, loff_t *ppos)
- {
- char val[TCP_CA_NAME_MAX];
- ctl_table tbl = {
- .data = val,//拥塞模块的名字
- .maxlen = TCP_CA_NAME_MAX,//拥塞模块的名字最长大小
- };
- int ret;
- tcp_get_default_congestion_control(val);//获得当前使用的拥塞控制,及链表的首节点
- ret = proc_dostring(&tbl, write, buffer, lenp, ppos);//将用户层buffer里面的东西拷贝到
- tbl.data,buffer里面保存着我们
- 通过sysctl -w设定的字符串
- if (write && ret == 0)
- ret = tcp_set_default_congestion_control(val);//将我们设定的拥塞控制放到链表的首位置
- return ret;
- }
- int tcp_set_default_congestion_control(const char *name)
- {
- struct tcp_congestion_ops *ca;
- int ret = -ENOENT;
- spin_lock(&tcp_cong_list_lock);
- ca = tcp_ca_find(name);//查找链表中是否已经存在将操作的拥塞控制模块
- #ifdef CONFIG_MODULES
- if (!ca && capable(CAP_NET_ADMIN)) {
- spin_unlock(&tcp_cong_list_lock);
- request_module("tcp_%s", name);
- spin_lock(&tcp_cong_list_lock);
- ca = tcp_ca_find(name);
- }
- #endif
- if (ca) {//模块存在
- ca->flags |= TCP_CONG_NON_RESTRICTED; /* default is always allowed */
- list_move(&ca->list, &tcp_cong_list);//将需要设置的模块插入链表的首位置
- ret = 0;
- }
- spin_unlock(&tcp_cong_list_lock);
- return ret;
- }
关于sysctl及procfs以后再分析
阅读(3081) | 评论(0) | 转发(0) |