内核版本:2.6.34

UDP报文接收
       UDP报文的接收可以分为两个部分:协议栈收到udp报文,插入相应队列中;用户调用recvfrom()或recv()系统调用从队列中取出报文,这里的队列就是sk->sk_receive_queue,它是报文中转的纽带,两部分的联系如下图所示。

第一部分:协议栈如何收取udp报文的。
      udp模块的注册在inet_init()中,当收到的是udp报文,会调用udp_protocol中的handler函数udp_rcv()。

[cpp] view plaincopy
  1. if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
  2. printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");

udp_rcv() -> __udp4_lib_rcv() 完成udp报文接收,初始化udp的校验和,并不验证校验和的正确性。

[cpp] view plaincopy
  1. if (udp4_csum_init(skb, uh, proto))
  2. goto csum_error;

在udptable中以套接字的[saddr, sport, daddr, dport]查找相应的sk,在上一篇中已详细讲过”sk的查找”,这里报文的source源端口相当于源主机的端口,dest目的端口相当于本地端口。

[cpp] view plaincopy
  1. sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);

如果udptable中存在相应的sk,即有socket在接收,则通过udp_queue_rcv_skb()将报文skb入队列,该函数稍后分析,总之,报文会被放到sk->sk_receive_queue队列上,然后sock_put()减少sk的引用计算,并返回。之后的接收工作的完成将有赖于用户的操作。

[cpp] view plaincopy
  1. if (sk != NULL) {
  2. int ret = udp_queue_rcv_skb(sk, skb);
  3. sock_put(sk);
  4. if (ret > 0)
  5. return -ret;
  6. return 0;
  7. }

当没有在udptable中找到sk时,则本机没有socket会接收它,因此要发送icmp不可达报文,在此之前,还要验证校验和udp_lib_checksum_complete(),如果校验和错误,则直接丢弃报文;如果校验和正确,则会增加mib中的统计,并发送icmp端口不可达报文,然后丢弃该报文。

[cpp] view plaincopy
  1. if (udp_lib_checksum_complete(skb))
  2. goto csum_error;
  3. UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
  4. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
  5. kfree_skb(skb);

udp_queue_rcv_skb() 报文入队列
      sock_woned_by_user()判断sk->sk_lock.owned的值,如果等于1,表示sk处于占用状态,此时不能向sk接收队列中添加skb,执行else if部分,sk_add_backlog()将skb添加到sk->sk_backlog队列上;如果等于0,表示sk没被占用,执行if部分,__udp_queue_rcv_skb()将skb添加到sk->sk_receive_queue队列上。

[cpp] view plaincopy
  1. bh_lock_sock(sk);
  2. if (!sock_owned_by_user(sk))
  3. rc = __udp_queue_rcv_skb(sk, skb);
  4. else if (sk_add_backlog(sk, skb)) {
  5. bh_unlock_sock(sk);
  6. goto drop;
  7. }
  8. bh_unlock_sock(sk);

那么何时sk会被占用?何时sk->sk_backlog上的skb被处理的?
      创建socket时,sys_socket() -> inet_create() -> sk_alloc() -> sock_lock_init() -> sock_lock_init_class_and_name()初始化sk->sk_lock_owned=0。
      比如当销毁socket时,udp_destroy_sock()会调用lock_sock()对sk加锁,操作完后,调用release_sock()对sk解锁。

[cpp] view plaincopy
  1. void udp_destroy_sock(struct sock *sk)
  2. {
  3. lock_sock(sk);
  4. udp_flush_pending_frames(sk);
  5. release_sock(sk);
  6. }

实际上,lock_sock()设置sk->sk_lock.owned=1;而release_sock()设置sk->sk_lock.owned=0,并处理sk_backlog队列上的报文,release_sock() -> __release_sock(),对于sk_backlog队列上的每个报文,调用sk_backlog_rcv() -> sk->sk_backlog_rcv()。同样是在socket的创建中,sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv()即__udp_queue_rcv_skb(),这个函数的作用上面已经讲过,将skb添加到sk_receive_queue,这样,所有的sk_backlog上的报文转移到了sk_receive_queue上。简单来说,sk_backlog队列的作用就是,锁定时报文临时存放在此,解锁时,报文移到sk_receive_queue队列。

第二部分:用户如何收取报文
      用户可以调用sys_recvfrom()或sys_recv()来接收报文,所不同的是,sys_recvfrom()可能通过参数获得报文的来源地址,而sys_recv()则不可以,但对接收报文并没有影响。在用户调用recvfrom()或recv()接收报文前,发给该socket的报文都会被添加到sk->sk_receive_queue上,recvfrom()和recv()要做的就是从sk_receive_queue上取出报文,拷贝到用户空间,供用户使用。
      sys_recv() -> sys_recvfrom()
      sys_recvfrom() -> sk->ops->recvmsg() 
                            ==> sock_common_recvmsg() -> sk->sk_prot->recvmsg() 
                            ==> udp_recvmsg()

sys_recvfrom()
      调用sock_recvmsg()接收udp报文,存放在msg中,如果接收到报文,从内核到用户空间拷贝报文的源地址到addr中,addr是recvfrom()调用的传入参数,表示报文源的地址。而报文的内容是在udp_recvmsg()中从内核拷贝到用户空间的。

[cpp] view plaincopy
  1. err = sock_recvmsg(sock, &msg, size, flags);
  2. if (err >= 0 && addr != NULL) {
  3. err2 = move_addr_to_user((struct sockaddr *)&address,
  4. msg.msg_namelen, addr, addr_len);
  5. if (err2 < 0)
  6. err = err2;
  7. }

udp_recvmsg() 接收udp报文
      这个函数有三个关键操作:
        1. 取到数据包 -- __skb_recv_datagram()
        2. 拷贝数据 -- skb_copy_datagram_iovec()或skb_copy_and csum_datagram_iovec()
        3. 必要时计算校验和 – skb_copy_and_csum_datagram_iovec()

__skb_recv_datagram(),它会从sk->sk_receive_queue上取出一个skb,前面已经分析到,内核收到发往该socket的报文会放在sk->sk_receive_queue。

skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0), &peeked, &err);

如果没有报文,有两种情况:使用了非阻塞接收,且用户接收时还没有报文到来;使用阻塞接收,但之前没有报文,且在sk->sk_rcvtimeo时间内都没有报文到来。没有报文,返回错误值。

[cpp] view plaincopy
  1. if (!skb)
  2. goto out;

len是recvfrom()传入buf的大小,ulen是报文内容的长度,如果ulen > len,那么只需要使用buf的ulen长度就可以了;如果len < ulen,那么buf不够报文填充,只能对报文截断,取前len个字节。

[cpp] view plaincopy
  1. ulen = skb->len - sizeof(struct udphdr);
  2. if (len > ulen)
  3. len = ulen;
  4. else if (len < ulen)
  5. msg->msg_flags |= MSG_TRUNC;

如果报文被截断或使用UDP-Lite,那么需要提前验证校验和,udp_lib_checksum_complete()完成校验和计算,函数在下面具体分析。

[cpp] view plaincopy
  1. if (len < ulen || UDP_SKB_CB(skb)->partial_cov) {
  2. if (udp_lib_checksum_complete(skb))
  3. goto csum_copy_err;
  4. }

如果报文不用验证校验和,那么执行if部分,调用skb_copy_datagram_iovec()直接拷贝报文到buf中就可以了;如果报文需要验证校验和,那么执行else部分,调用skb_copy_and_csum_datagram_iovec()拷贝报文到buf,并在拷贝过程中计算校验和。这也是为什么在内核收到udp报文时为什么先验证校验和再处理的原因,udp报文可能很大,校验和的计算可能很耗时,将其放在拷贝过程中可以节约开销,当然它的代价是一些校验和错误的报文也会被添加到socket的接收队列上,直到用户真正接收时它们才会被丢弃。

if (skb_csum_unnecessary(skb))  err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov, len);
else {  err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);  if (err == -EINVAL)  goto csum_copy_err;
}

拷贝地址到msg->msg_name中,在sys_recvfrom()中msg->msg_name=&address,然后address会从内核拷贝给用户空间的addr。

[cpp] view plaincopy
  1. if (sin) {
  2. sin->sin_family = AF_INET;
  3. sin->sin_port = udp_hdr(skb)->source;
  4. sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
  5. memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
  6. }

下面来重点看核心操作的三个函数:
__skb_recv_datagram()   从sk_receive_queue上取一个skb
      核心代码段如下,skb_peek()从sk->sk_receive_queue中取出一个skb,如果有的话,则返回skb,作为用户此次接收的报文,当然还有对skb的后续处理,但该函数只是取出一个skb;如果还没有的话,则使用wait_for_packet()等待报文到来,其中参数timeo代表等待的时间,如果使用非阻塞接收的话,timeo会设置为0(即当前没有skb的话则直接返回,不进行等待),否则设置为sk->sk_rcvtimeo。

[cpp] view plaincopy
  1. do {
  2. ……
  3. skb = skb_peek(&sk->sk_receive_queue);
  4. if (skb) {
  5. *peeked = skb->peeked;
  6. if (flags & MSG_PEEK) {
  7. skb->peeked = 1;
  8. atomic_inc(&skb->users);
  9. } else
  10. __skb_unlink(skb, &sk->sk_receive_queue);
  11. }
  12. if (skb)
  13. return skb;
  14. ……
  15. } while (!wait_for_packet(sk, err, &timeo));

skb_copy_datagram_iovec()   拷贝skb内容到msg中
      拷贝可以分三部分:线性地址空间的拷贝,聚合/发散地址空间的拷贝,非线性地址空间的拷贝。第二部分需要硬件的支持,这里讨论另两部分。
      在skb的buff中的是线性地址空间,在skb的frag_list上的是非线性地址空间;当没有分片发生的,用线性地址空间就足够了,但是当报文过长而分片时,第一个分片会使用线性地址空间,其余的分片将被链到skb的frag_list上,即非线性地址空间,具体可以参考”ipv4模块”中分片部分。
      拷贝报文内容时,就要将线性和非线性空间的内容都拷贝过去。下面是拷贝线性地址空间的代码段,start是报文的线性部分长度(skb->len-skb->datalen),copy是线性地址空间的大小,offset是相对skb的偏移(即此次拷贝从哪里开始),以udp报文为例,这几个值如下图所示。memcpy_toiovec()拷贝内核到to中,要注意的是它改变了to的成员变量。

[cpp] view plaincopy
  1. int start = skb_headlen(skb);
  2. int i, copy = start - offset;
  3. if (copy > 0) {
  4. if (copy > len)
  5. copy = len;
  6. if (memcpy_toiovec(to, skb->data + offset, copy))
  7. goto fault;
  8. if ((len -= copy) == 0)
  9. return 0;
  10. offset += copy;
  11. }

下面是拷贝非线性地址空间的代码段,遍历skb的frag_list链表,对上面的每个分片,拷贝内容到to中,这里start, end的值不重要,重要的是它们的差值end-start,表示了当前分片frag_iter的长度,使用skb_copy_datagram_iovec()拷贝当前分片内容,即把每个分片都作为单独报文来处理。不过对于分片,感觉只有拷贝的第一部分和第二部分,在IP层分片重组时,并没有将分片链在分片的frag_list上的情况,而都链在头分片的frag_list上。

skb_walk_frags(skb, frag_iter) {  int end;  end = start + frag_iter->len;  if ((copy = end - offset) > 0) {  if (copy > len)  copy = len;  if (skb_copy_datagram_iovec(frag_iter, offset - start, to, copy))  goto fault;  if ((len -= copy) == 0)  return 0;  offset += copy;  }  start = end;
} 

还是以一个例子来说明,主机收到一个udp报文,内容长度为4000 bytes,MTU是1500,传入buff数组大小也为4000。根据MTU,报文会会被分成三片,分片IP报内容大小依次是1480, 1480, 1040。每个分片都有一个20节字的IP报文,第一个分片还有一个8节字的udp报头。接收时数据拷贝情况如下:

分片一是第一个分片,包含UDP报文,在拷贝时要跳过,因为使用的是udp socket接收,只要报文内容就可以了。三张图片代表了三次调用skb_copy_datagram_iovec()的情况,iov是存储内容的buff,最终结果是三个分片共4000字节拷贝到了iov中。
memcpy_toiovec()函数需要注意,不仅因为它改变了iovec的成员值,还因为最后的iov++。在udp socket的接收recvfrom()中,msg.msg_iov = &iov,而iov定义成struct iovec iov,即传入参数iov实际只有一个的空间,那么在iov++后,iov将指向非法的地址。这里只考虑udp使用时的情况,memcpy_toiovec()调用的前一句是,这里len是接收buff的长度:

[cpp] view plaincopy
  1. if (copy > len)
  2. copy = len;

而memcpy_toiovec()中又有int copy = min_t(unsigned int, iov->iov_len, len),这里len是上面传入的copy,iov_len是接收buff长度,这两句保证了函数中copy值与len相等,即完成一次拷贝后,len-=copy会使len==0,虽然iov++指向了非法内存,但由于while(len > 0)已退出,所以不会使用iov做任何事情。其次,函数中的iov++并不会对参数iov产生影响,即函数完成iov还是传入的值。最后,拷贝完后会修改iov_len和iov_base的值,iov_len表示可用长度,iov_base表示起始拷贝位置。

[cpp] view plaincopy
  1. int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len)
  2. {
  3. while (len > 0) {
  4. if (iov->iov_len) {
  5. int copy = min_t(unsigned int, iov->iov_len, len);
  6. if (copy_to_user(iov->iov_base, kdata, copy))
  7. return -EFAULT;
  8. kdata += copy;
  9. len -= copy;
  10. iov->iov_len -= copy;
  11. iov->iov_base += copy;
  12. }
  13. iov++;
  14. }
  15. return 0;
  16. }

skb_copy_and_csum_datagram_iovec()   拷贝skb内容到msg中,同时计算校验和
      这个函数提高了校验和计算效率,因为它合并了拷贝与计算操作,这样只要一次遍历操作就可以了。与skb_copy_datagram_iovec()相比,它在每次拷贝skb内容时,计算下这次拷贝内容的校验和。

[cpp] view plaincopy
  1. csum = csum_partial(skb->data, hlen, skb->csum);
  2. if (skb_copy_and_csum_datagram(skb, hlen, iov->iov_base, chunk, &csum))
  3. goto fault;

UDP报文发送
      发送时有两种调用方式:sys_send()和sys_sendto(),两者的区别在于sys_sendto()需要给入目的地址的参数;而sys_send()调用前需要调用sys_connect()来绑定目的地址信息;两者的后续调用是相同的。如果调用sys_sendto()发送,地址信息在sys_sendto()中从用户空间拷贝到内核空间,而报文内容在udp_sendmsg()中从用户空间拷贝到内核空间。
      sys_send() -> sys_sendto()
      sys_sendto() -> sock_sendmsg() -> __sock_sendmsg() -> sock->ops->sendmsg()
                         ==> inet_sendmsg() -> sk->sk_prot->sendmsg()
                         ==> udp_sendmsg()
      udp_sendmsg()的核心流程如下图所示,只列出了核心的函数调用了参数赋值,大致步骤是:获取信息 -> 获取路由项rt -> 添加数据 -> 发送数据。

udp_sock结构体中的pending用于标识当前udp_sock上是否有待发送数据,如果有的话,则直接goto do_append_data继续添加数据;否则先要做些初始化工作,再才添加数据。实际上,pending!=0表示此调用前已经有数据在udp_sock中的,每次调和sendto()发送数据时,pending初始等于0;在添加数据时,设置up->pending = AF_INET。直到最后调用udp_push_pending_frames()将数据发送给IP层或skb_queue_empty(&sk->sk_write_queue)发送链表上为空,这时设置up->pending = 0。因此,这里可以看到,报文发送时pending值的变化:

通常使用sendto()发送都是一次调用对应一个报文,即pending=0->AF_INET->0;但如果调用sendto()时参数用到了MSG_MORE标志,则pending=0->AF_INET,直到调用sendto()时未使用MSG_MORE标志,表示此次发送数据是最后一部分数据时,pending=AF_INET->0。

[cpp] view plaincopy
  1. if (up->pending) {
  2. lock_sock(sk);
  3. if (likely(up->pending)) {
  4. if (unlikely(up->pending != AF_INET)) {
  5. release_sock(sk);
  6. return -EINVAL;
  7. }
  8. goto do_append_data;
  9. }
  10. release_sock(sk);
  11. }

如果pending=0没有待发送数据,执行初始化操作:报文长度、地址信息、路由项。
      ulen初始为sendto()传入的数据长度,由于是第一部分数据(如果没有后续数据,则就是报文),ulen要添加udp报头的8字节。

[cpp] view plaincopy
  1. ulen += sizeof(struct udphdr);

这段代码获取要发送数据的目的地址和端口号。一种情况是调用sendto()发送数据,此时目的的信息以参数传入,存储在msg->msg_name中,因此从中取出daddr和dport;另一种情况是调用connect(), send()发送数据,在connect()调用时绑定了目的的信息,存储在inet中,并且由于是调用了connect(),sk->sk_state会设置为TCP_ESTABLISHED。以后调用send()发送数据时,无需要再给入目的信息参数,因此从inet中取出dadr和dport。而connected表示了该socket是否已绑定目的。

[cpp] view plaincopy
  1. if (msg->msg_name) {
  2. struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;
  3. if (msg->msg_namelen < sizeof(*usin))
  4. return -EINVAL;
  5. if (usin->sin_family != AF_INET) {
  6. if (usin->sin_family != AF_UNSPEC)
  7. return -EAFNOSUPPORT;
  8. }
  9. daddr = usin->sin_addr.s_addr;
  10. dport = usin->sin_port;
  11. if (dport == 0)
  12. return -EINVAL;
  13. } else {
  14. if (sk->sk_state != TCP_ESTABLISHED)
  15. return -EDESTADDRREQ;
  16. daddr = inet->inet_daddr;
  17. dport = inet->inet_dport;
  18. connected = 1;
  19. }

下一步是获取路由项rt,如果已连接(调用过connect),则路由信息在connect()时已获取,直接拿就可以了;如果未连接或拿到的路由项已被删除,则需要重新在路由表中查找,还是使用ip_route_output_flow()来查找,如果是连接状态的socket,则要用新找到的rt来更新socket,当然,前提条件是之前的rt已过期。

[cpp] view plaincopy
  1. if (rt == NULL) {
  2. ……
  3. err = ip_route_output_flow(net, &rt, &fl, sk, 1);
  4. ……
  5. if (connected)
  6. sk_dst_set(sk, dst_clone(&rt->u.dst));
  7. }

存储信息daddr, dport, saddr, sport到cork.fl中,它们会在生成udp报头和计算udp校验和时用到。up->pending=AF_INET标识了数据添加的开始,下面将开始数据的添加工作。

[cpp] view plaincopy
  1. inet->cork.fl.fl4_dst = daddr;
  2. inet->cork.fl.fl_ip_dport = dport;
  3. inet->cork.fl.fl4_src = saddr;
  4. inet->cork.fl.fl_ip_sport = inet->inet_sport;
  5. up->pending = AF_INET;

如果pending!=0或执行完初始化操作,则直接执行添加数据操作:
      up->len表示要发送数据的总长度,包括udp报头,因此每发送一部分数据就要累加它的长度,在发送后up->len被清0。然后调用ip_append_data()添加数据到sk->sk_write_queue,它会处理数据分片等问题,在 ”ICMP模块” 中有详细分析过。

up->len += ulen;
getfrag  =  is_udplite ?  udplite_getfrag : ip_generic_getfrag;
err = ip_append_data(sk,getfrag,msg->msg_iov,ulen,sizeof(struct udphdr),&ipc,&rt,corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags); 

ip_append_data()添加数据正确会返回0,否则udp_flush_pending_frames()丢弃将添加的数据;如果添加数据正确,且没有后续的数据到来(由MSG_MORE来标识),则udp_push_pending_frames()将数据发送给IP层,下面将详细分析这个函数。最后一种情况是当sk_write_queue上为空时,它触发的条件必须是发送多个报文且sk_write_queue上为空,而实际上在ip_append_data过后sk_write_queue不会为空的,因此正常情况下并不会发生。哪种情况会发生呢?重置pending值为0就是在这里完成的,三个条件语句都会将pending设置为0。

[cpp] view plaincopy
  1. if (err)
  2. udp_flush_pending_frames(sk);
  3. else if (!corkreq)
  4. err = udp_push_pending_frames(sk);
  5. else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
  6. up->pending = 0;

数据已经处理完成,释放取到的路由项rt,如果有IP选项,也释放它。如果发送数据成功,返回发送的长度len;否则根据错误值err进行错误处理并返回err。

ip_rt_put(rt);
if (free)  kfree(ipc.opt);
if (!err)  return len;
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {  UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;

在 “ICMP模块” 中往IP层发送数据使用的是ip_push_pending_frames()。而在UDP模块中往IP层发送数据使用的是ip_push_pending_frames()。而在UDP模块中往IP层发送数据的udp_push_pending_frames()只是对ip_push_pending_frames()的封装,主要是增加对UDP的报头的处理。同理,udp_flush_pending_frames()也是,只是它更简单,仅仅重置了up->len和up->pending的值,重置后可以开始一个新报文。那么udp_push_pending_frames()封装了哪些处理呢。

udp_push_pending_frames() 发送数据给IP层
      设置udp报头,包括源端口source,目的端口dest,报文长度len。

[cpp] view plaincopy
  1. uh = udp_hdr(skb);
  2. uh->source = fl->fl_ip_sport;
  3. uh->dest = fl->fl_ip_dport;
  4. uh->len = htons(up->len);
  5. uh->check = 0;

计算udp报头中的校验和,包括了伪报头、udp报头和报文内容。

if (is_udplite)  csum  = udplite_csum_outgoing(sk, skb);
else if (sk->sk_no_check == UDP_CSUM_NOXMIT) {   /* UDP csum disabled */  skb->ip_summed = CHECKSUM_NONE;  goto send;
} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */  udp4_hwcsum_outgoing(sk, skb, fl->fl4_src, fl->fl4_dst, up->len);  goto send;
} else       /*   `normal' UDP    */  csum = udp_csum_outgoing(sk, skb);
uh->check = csum_tcpudp_magic(fl->fl4_src, fl->fl4_dst, up->len, sk->sk_protocol, csum); 

将报文发送给IP层,这个函数已经分析过了。

[cpp] view plaincopy
  1. err = ip_push_pending_frames(sk);

同样,在发送完报文后,重置len和pending的值,以便开始下一个报文发送。

[cpp] view plaincopy
  1. up->len = 0;
  2. up->pending = 0;

Linux内核分析 - 网络[十二]:UDP模块 - 收发相关推荐

  1. Linux内核分析 - 网络[十二]:UDP模块 - socket

    内核版本:2.6.34 这部分内容在于说明socket创建后如何被内核协议栈访问到,只关注两个问题:sock何时插入内核表的,sock如何被内核访问的.对于核心的sock的插入.查找函数都给出了流程图 ...

  2. Linux内核分析 - 网络[十六]:TCP三次握手

    内核:2.6.34       TCP是应用最广泛的传输层协议,其提供了面向连接的.可靠的字节流服务,但也正是因为这些特性,使得TCP较之UDP异常复杂,还是分两部分[创建与使用]来进行分析.这篇主要 ...

  3. Linux内核分析 - 网络[十一]:ICMP模块

    内核版本:2.6.34 ICMP模块比较简单,要注意的是icmp的速率限制策略,向IP层传输数据ip_append_data()和ip_push_pending_frames(). 在net/ipv4 ...

  4. Linux内核分析 - 网络[十]:ARP杂谈

    内核版本:2.6.34 杂谈一:重复地址检测 Linux协议栈中处理重复地址检测报文的是arp_process()中的一段代码,RFC2131是DHCP的草案,相应的sip==0是DHCP服务器用来检 ...

  5. Linux内核分析 - 网络[十四]:IP选项

    内核版本:2.6.34       在发送报文时,可以调用函数setsockopt()来设置相应的选项,本文主要分析IP选项的生成,发送以及接收所执行的流程,选取了LSRR为例子进行说明,主要分为选项 ...

  6. Linux内核分析 - 网络[十五]:陆由表[再议]

    内核版本:2.6.34 陆由表作为三层协议的核心数据结构,理解它是至关重要的.前面已经分析过路由表,有兴趣的可以参考:       第一篇:路由表 http://blog.csdn.net/qy532 ...

  7. linux内核分析 网络九,“Linux内核分析”实验报告(九)

    一 Linux内核分析博客简介及其索引 本次实验简单的分析了计算机如何进行工作,并通过简单的汇编实例进行解释分析 在本次实验中 通过听老师的视频分析,和自己的学习,初步了解了进程切换的原理.操作系统通 ...

  8. Linux内核实验孟宁,《linux内核分析》实验二:时间片轮转多道程序运行原理

    一.概述 本文通过分析一个简单的时间片轮转多道程序的内核 mykernel,来理解操作系统是如何工作的. mykernel 是孟宁老师的一个开源项目,借助 Linux 内核部分源代码模拟存储程序计算机 ...

  9. 《Linux内核分析》(二)——从一个简单Linux内核分析进程切换原理

    转载:https://blog.csdn.net/FIELDOFFIER/article/details/44280717 <Linux内核分析>MOOC课程http://mooc.stu ...

最新文章

  1. Mysql字符串处理
  2. Java多线程-synchronized关键字
  3. 青龙羊毛——去闲转(教程)
  4. 浅谈常用的Web安全技术手段
  5. 通俗解释随机森林算法
  6. 交叉报表问题 subDataset
  7. Cacti迁移RRA数据迁移脚本
  8. java 两个字段排序,如何在Java中按两个字段排序?
  9. 2.[Yii]创建与设置默认控制器及载入模板
  10. 1245C. Constanze‘s Machine
  11. 惠普g260鼠标宏软件_电竞外设再添新成员,微星DS102电竞鼠标正式发布
  12. python全栈 操作系统
  13. library/adodb/adodb.inc.php,ADOdb Library for PHP
  14. 计算机服务添加打印机服务,无法添加打印机报错后台程序服务没有运行的解决方法...
  15. android 字体设置为中等粗细
  16. android 定制ROM集成 YouTube API,并实现双屏异显(主屏展示列表,副屛播放视频)
  17. ECU重编程流程(UDS)
  18. 微软行星云计算Planetary Computer——previsa南美洲亚马逊雨林森林损坏系统AI智能评估
  19. ttys和tty_Linux中tty、pty、/dev/ttySn等概念讲解
  20. ccf 考试时间_梳理丨2020年五大学科竞赛考试时间安排出炉!

热门文章

  1. idea热部署devtools
  2. 调参1——随机森林贝叶斯调参
  3. linux下安装nginx tar包,Linux环境下Nginx的安装
  4. C#2.0泛型中的变化: default 关键字
  5. window7安装MongoDB详细步骤
  6. 去除input填充颜色
  7. micro 架构组件介绍
  8. poj3264Balanced Lineup(RMQ)
  9. RestartOnCrash一个监控进程的小工具,可用于监控iis/apache/mysql等程序
  10. 类型的权限已失败 SqlClientPermission