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

sock如何插入内核表
      socket创建后就可以用来与外部网络通信,用户可以通过文件描述符fd来找到要操作的socket,内核则通过查表来找到要操作的socket。这意味着socket创建时会在文件系统中生成相应项,同时还会插入到存储socket的表中,方便用户和内核通过两种方式进行访问。
      以创建如下udp socket为例,这里的创建仅仅指定socket的协议簇是AF_INET,类型是SOCK_DGRAM,协议是0,此时创建了socket,相应文件描述符,但仍缺少其它信息,此时socket并未插入到内核表中,还是处于游离态,除了用户通过fd操作,内核是看不到的socket的。

[cpp] view plaincopy
  1. fd = socket(AF_INET, SOCK_DGRAM, 0);

根据作为的角色(服务器或客户端)不同,接下来执行的动作也不相同。这两句分条时服务器和客户端与外部通信的第一句,执行后,与外部连接建立,socket的插入内核表也是由这两句触发的。
      服务器端udp socket

[cpp] view plaincopy
  1. bind(fd, &serveraddr, sizeof(serveraddr));

客户端udp socket

[cpp] view plaincopy
  1. sendto(fd, buff, len, 0, &serveraddr, sizeof(serveraddr));

下面来看下创建socket的具体动作,只涉及与socket存储相关的代码,这些系统调用的其它方面以后再具体分析。
      sys_socket() 创建socket,映射文件描述符fd

[cpp] view plaincopy
  1. retval = sock_create(family, type, protocol, &sock);
  2. retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

在内核中,有struct socket,也就是通常所说的socket,表示网络的接口,还有struct sock,则是AF_INET域的接口。一般struct socket成员叫sock,struct sock成员叫sk,在代码中不要混淆。
      sock_create() -- > __sock_create() 
      最终执行__sock_create()来创建,注意__sock_create()最后一个参数是0,表示是由用户创建的;如果是1,则表示是由内核创建的。
      分配socket并设置sock->type为SOCK_DGRAM。

[cpp] view plaincopy
  1. sock = sock_alloc();
  2. sock->type = type;

从net_families中取得AF_INET(也即PF_INET)协议族的参数,net_families数组存储不同协议族的参数,像AF_INET协议族是在加载IP模块时注册的,inet_init() -> sock_register(&inet_family_ops),sock_register()就是将参数加入到net_families数组中,inet_family_ops定义如下:

[cpp] view plaincopy
  1. pf = rcu_dereference(net_families[family]);
  2. static const struct net_proto_family inet_family_ops = {
  3. .family = PF_INET,
  4. .create = inet_create,
  5. .owner = THIS_MODULE,
  6. };

最后调用相应协议簇的创建方法,这里的pf->create()就是inet_create(),它创建INET域的结构sock。

[cpp] view plaincopy
  1. err = pf->create(net, sock, protocol, kern);

从__sock_create()代码看到创建包含两步:sock_alloc()和pf->create()。sock_alloc()分配了sock内存空间并初始化inode;pf->create()初始化了sk。

sock_alloc()
       分配空间,通过new_inode()分配了节点(包括socket),然后通过SOCKET_I宏获得sock,实际上inode和sock是在new_inode()中一起分配的,结构体叫作sock_alloc。

[cpp] view plaincopy
  1. inode = new_inode(sock_mnt->mnt_sb);
  2. sock = SOCKET_I(inode);

设置inode的参数,并返回sock。

[cpp] view plaincopy
  1. inode->i_mode = S_IFSOCK | S_IRWXUGO;
  2. inode->i_uid = current_fsuid();
  3. inode->i_gid = current_fsgid();
  4. return sock;

继续往下看具体的创建过程:new_inode(),在分配后,会设置i_ino和i_state的值。

[cpp] view plaincopy
  1. struct inode *new_inode(struct super_block *sb)
  2. {
  3. ……
  4. inode = alloc_inode(sb);
  5. if (inode) {
  6. spin_lock(&inode_lock);
  7. __inode_add_to_lists(sb, NULL, inode);
  8. inode->i_ino = ++last_ino;
  9. inode->i_state = 0;
  10. spin_unlock(&inode_lock);
  11. }
  12. return inode;
  13. }

其中的alloc_inode() -> sb->s_op->alloc_inode(),sb是sock_mnt->mnt_sb,所以alloc_inode()指向的是sockfs的操作函数sock_alloc_inode。

[cpp] view plaincopy
  1. static const struct super_operations sockfs_ops = {
  2. .alloc_inode = sock_alloc_inode,
  3. .destroy_inode =sock_destroy_inode,
  4. .statfs = simple_statfs,
  5. };

sock_alloc_inode()中通过kmem_cache_alloc()分配了struct socket_alloc结构体大小的空间,而struct socket_alloc结构体定义如下,但只返回了inode,实际上socket和inode都已经分配了空间,在之后就可以通过container_of取到socket。

[cpp] view plaincopy
  1. static struct inode *sock_alloc_inode(struct super_block *sb)
  2. {
  3. struct socket_alloc *ei;
  4. ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
  5. …..
  6. return &ei->vfs_inode;
  7. }
  8. struct socket_alloc {
  9. struct socket socket;
  10. struct inode vfs_inode;
  11. };

inet_create()
      从inetsw中根据类型、协议查找相应的socket interface。

[cpp] view plaincopy
  1. list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
  2. ……
  3. if (IPPROTO_IP == answer->protocol)
  4. break;
  5. ……
  6. }

inetsw是在inet_init()时被注册的,有三种:tcp, udp, raw,由于我们创建的是udp socket,所以查到的是第二项,udp_prot。

[cpp] view plaincopy
  1. static struct inet_protosw inetsw_array[] =
  2. {
  3. {
  4. .type =       SOCK_STREAM,
  5. .protocol =   IPPROTO_TCP,
  6. .prot =       &tcp_prot,
  7. .ops =        &inet_stream_ops,
  8. .no_check =   0,
  9. .flags =      INET_PROTOSW_PERMANENT |
  10. INET_PROTOSW_ICSK,
  11. },
  12. {
  13. .type =       SOCK_DGRAM,
  14. .protocol =   IPPROTO_UDP,
  15. .prot =       &udp_prot,
  16. .ops =        &inet_dgram_ops,
  17. .no_check =   UDP_CSUM_DEFAULT,
  18. .flags =      INET_PROTOSW_PERMANENT,
  19. },
  20. {
  21. .type =       SOCK_RAW,
  22. .protocol =   IPPROTO_IP, /* wild card */
  23. .prot =       &raw_prot,
  24. .ops =        &inet_sockraw_ops,
  25. .no_check =   UDP_CSUM_DEFAULT,
  26. .flags =      INET_PROTOSW_REUSE,
  27. }
  28. };

sock->ops指向inet_dgram_ops,然后创建sk,sk->proto指向udp_prot,注意这里分配的大小是struct udp_sock,而不仅仅是struct sock大小。

[cpp] view plaincopy
  1. sock->ops = answer->ops;
  2. ……
  3. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);

然后设置inet的一些参数,这里直接将sk类型转换为inet,因为在sk_alloc()中分配的是struct udp_sock结构大小,返回的是struct sock,利用了第一个成员的特性,三者之间的关系如下图:

[cpp] view plaincopy
  1. inet = inet_sk(sk);
  2. …..
  3. inet->inet_id = 0;

此时sock和sk都已经分配了空间,再设置sock与sk关系,即sock->sk=sk,并做一些初始化操作,如sk的队列初始化。初后调用sk_prot->init(),inet_dgram_ops->init()为NULL,这里没做任何事情。

[cpp] view plaincopy
  1. sock_init_data(sock, sk);
  2. if (sk->sk_prot->init) {
  3. err = sk->sk_prot->init(sk);
  4. if (err)
  5. sk_common_release(sk);
  6. }

当创建的是一个SOCK_RAW类型的socket时,还会额外执行下列语句。当协议值赋给inet->inet_num与inet->inet_sport,然后sk->sk_prot->hash(sk)将sk插入到内核的sock表中,使用的索引值是协议号。这个可以这样理解,如果创建的是UDP或TCP的socket,它们是标准的套接字,用[sip, sport, tip, tport]这样的四元组来查找,socket()时还缺少这些信息,还不能插入到内核的sock表中。但如果创建的是RAW的socket,它只属于某一特定协议,查找它使用的应是协议号而不是套接字的四元组,因此,socket()时就通过hash()插入到内核sock表中。

[cpp] view plaincopy
  1. if (SOCK_RAW == sock->type) {
  2. inet->inet_num = protocol;
  3. if (IPPROTO_RAW == protocol)
  4. inet->hdrincl = 1;
  5. }
  6. if (inet->inet_num) {
  7. inet->inet_sport = htons(inet->inet_num);
  8. sk->sk_prot->hash(sk);
  9. }

那么sock是在什么时候插入到内核表中的,答案是sk->sk_prot->get_port()函数,对于UDP来讲,它指向udp_v4_get_port()函数,根据服务器和客户端的行为不同,bind()和sendto()都会调用到get_port(),也就是说,在bind()或sendto()调用时,sock才被插入到内核表中。
bind() 绑定地址
      sys_bind() -> sock->ops->bind() -> inet_bind() -> sk->sk_prot->get_port()
      sk->sk_prot是udp_prot,这里实际调用udp_v4_get_port()函数。

sendto() 发送到指定地址
      sys_sendto() -> sock_sendmsg() -> __sock_sendmsg()() -> sock->ops->sendmsg()
      由于创建的是udp socket,因此sock->ops指向inet_dgram_ops,sendmsg()实际调用inet_sendmsg()函数。该函数中的有如下语句:

[cpp] view plaincopy
  1. if (!inet_sk(sk)->inet_num && inet_autobind(sk))
  2. return -EAGAIN;

客户端在执行sendto()前仅仅执行了socket()操作,此时inet_num=0,因此执行了inet_autobind(),该函数会调用sk->sk_prot->get_port()。从而回到了udp_v4_get_port()函数,它会将sk插入到内核表udp_table中。

下面重点看下插入sk的函数udp_v4_get_port():
udp_v4_get_port() 插入sk到内核表udptable中
      哈希值hash2_nulladdr由[INADDR_ANY, snum]得到,hash2_partial由[inet_rcv_saddr, 0]得到,即前者用本地端口作哈希,后者用本地地址作哈希。udp_portaddr_hash存储后者的值hash2_partial,便于计算最后的哈希值。

unsigned int hash2_nulladdr = udp4_portaddr_hash(sock_net(sk), INADDR_ANY, snum);
unsigned int hash2_partial = udp4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0);
udp_sk(sk)->udp_portaddr_hash = hash2_partial;

最后调用udp_lib_get_port(),ipv4_rcv_saddr_equal()是比较地址是否相等的函数,snum是本地端口,hash2_nulladdr是由它得到的哈杀值,sk是要插入的表项。

return udp_lib_get_port(sk, snum, ipv4_rcv_saddr_equal, hash2_nulladdr);

udp_lib_get_port()
      取得内核存放sock的表,对于udp socket来说,就是udp_table,它在udp_prot中被定义。在udp_table的创建过程中已经看到,udp_table有两个hash表:hash和hash2,两者大小相同,只是前者用snum作哈希值,后者用saddr, snum作哈希值。使用两个hash表的目的在于加速查找,先用snum在hash中查找,再用saddr, snum在hash2中查找,最后根据效率决定在hash或hash2中查找。

[cpp] view plaincopy
  1. struct udp_table *udptable = sk->sk_prot->h.udp_table;

根据snum的不同会执行不同的操作,snum为0则先选择一个可用端口号,再插入;snum不为0则先确定之前没有存储相应sk,再插入。

[cpp] view plaincopy
  1. if (!snum) {
  2. snum==0代码段
  3. } else {
  4. snum!=0代码段
  5. }

如果snum!=0,此时执行else部分代码。hslot是从udp_table中hash表取出的表项,键值是snum。

[cpp] view plaincopy
  1. hslot = udp_hashslot(udptable, net, snum);

如果hslot->count大于10,即在hash表中以snum为键值的项的数目在于10,此时改用在hash2表中查找。如果hslot->count不足10,那么直接在hash表中查找就可以了。这样划分是出于效率的考虑。
      先看数目大于10的情况,hslot2是udptable中hash2表取出的表项,键值是[inet_rcv_addr, snum],如果hslot2项的数目比hslot还多,那么查找hash2表是不划算的,返回直接查找hash表。如果hslot2更少(这也是设计hash2的目的),使用udp_lib_lport_inuse2()查找是否有匹配项;如果没有找到,则使用新的键值hash2_nulladdr,即[INADDR_ANY, snum]从hash2中取出表项,再使用udp_lib_lport_inuse2()查找是否有匹配项。如果有,表明要插入的sk已经存在于内核表中,直接返回;如果没有,则执行sk的插入操作。scan_primary_hash代码段是在hash表的hslot项中查找,只有当在hash2中查找更费时时才会执行。

[cpp] view plaincopy
  1. if (hslot->count > 10) {
  2. int exist;
  3. unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;
  4. slot2          &= udptable->mask;
  5. hash2_nulladdr &= udptable->mask;
  6. hslot2 = udp_hashslot2(udptable, slot2);
  7. if (hslot->count < hslot2->count)
  8. goto scan_primary_hash;
  9. exist = udp_lib_lport_inuse2(net, snum, hslot2, sk, saddr_comp);
  10. if (!exist && (hash2_nulladdr != slot2)) {
  11. hslot2 = udp_hashslot2(udptable, hash2_nulladdr);
  12. exist = udp_lib_lport_inuse2(net, snum, hslot2,
  13. sk, saddr_comp);
  14. }
  15. if (exist)
  16. goto fail_unlock;
  17. else
  18. goto found;
  19. }
  20. scan_primary_hash:
  21. if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
  22. saddr_comp, 0))
  23. goto fail_unlock;
  24. }

流程图:

如果snum==0,即没有绑定本地端口,此时执行if部分代码段,这种情况一般发生在客户端使用socket,此时内核会为它选择一个未使用的端口,下面来看下内核选择临时端口的策略。
      在说明下列参数含义前要先弄清楚udptable中hash公式:(num + net_hash_mix(net)) & mask,net_hash_mix(net)返回一般为0,hash公式可简写为num&mask。即本地端口对udptable大小取模。因此表项是循环、均匀地分布在hash表中的。假设udptable大小为8,现插入16个表项,结果会如下图:

声明bitmap数组,大小为udp_table每个键值最多存储的表项,即最大端口号/哈希表大小。端口号的值规定范围是1-65536,而哈希表一般大小是256,因此实际分配bitmap[8]。low和high代表可用本地端口的下限和上限;remaining代表位于low和high间的端口号数目。用随机值rand生成first,注意它是unsigned short类型,16位,表示起始查找位置;last表示终止查找位置,first和last相差表大小保证了所有键值都会被查询一次。随机值rand最后处理成哈希表大小的奇数倍,之所以要是奇数倍,是为了保证哈希到同一个键值的所有端口号都能被遍历,可以试着1开始,每次+2和每次+3,直到回到1,所遍历的数有哪些不同,就会明白rand处理的意义。

[cpp] view plaincopy
  1. DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);
  2. inet_get_local_port_range(&low, &high);
  3. remaining = (high - low) + 1;
  4. rand = net_random();
  5. first = (((u64)rand * remaining) >> 32) + low;
  6. rand = (rand | 1) * (udptable->mask + 1);
  7. last = first + udptable->mask + 1;

使用first值作为端口号,从udptable的hash表中找到hslot项,重置bitmap数组全0,调用函数udp_lib_lport_inuse()遍历hslot项的所有表项,将所有已经使用的sport对应于bitmap的位置置1。

[cpp] view plaincopy
  1. do {
  2. hslot = udp_hashslot(udptable, net, first);
  3. bitmap_zero(bitmap, PORTS_PER_CHAIN);
  4. spin_lock_bh(&hslot->lock);
  5. udp_lib_lport_inuse(net, snum, hslot, bitmap, sk,
  6. addr_comp, udptable->log);

此时bitmap中包含了所有哈希到hslot的端口的使用情况,下面要做的就是从first位置开始,每次递增rand(保证哈希值不变),查找符合条件的端口:端口在low~high的可用范围内;端口还没有被占用。do{}while循环的判断条件snum!=first和snum+=rand一起保证了所有哈希到hslot的端口号都会被遍历到。如果找到了可用端口号,即跳出,执行插入sk的操作,否则++first,查找下一个键值,直到fisrt==last,表明所有键值都已轮循一遍,仍没有结果,则退出,sk插入失败。

[cpp] view plaincopy
  1. snum = first;
  2. do {
  3. if (low <= snum && snum <= high &&
  4. !test_bit(snum >> udptable->log, bitmap))
  5. goto found;
  6. snum += rand;
  7. } while (snum != first);
  8. spin_unlock_bh(&hslot->lock);
  9. } while (++first != last);
  10. goto fail;

流程图:

当没有在当前内核udp_table中找到匹配项时,执行插入新sk的操作。首先给sk参数赋值:inet_num, udp_port_hash, udp_portaddr_hash。然后将sk加入到hash表和hash2表中,并增加相应计数。

[cpp] view plaincopy
  1. found:
  2. inet_sk(sk)->inet_num = snum;
  3. udp_sk(sk)->udp_port_hash = snum;
  4. udp_sk(sk)->udp_portaddr_hash ^= snum;
  5. if (sk_unhashed(sk)) {
  6. sk_nulls_add_node_rcu(sk, &hslot->head);
  7. hslot->count++;
  8. sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
  9. hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
  10. spin_lock(&hslot2->lock);
  11. hlist_nulls_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
  12. &hslot2->head);
  13. hslot2->count++;
  14. spin_unlock(&hslot2->lock);
  15. }

sock如何被内核访问
      创建的udp socket成功后,当使用该socket与外部通信时,协议栈会收到发往该socket的udp报文。
      udp_rcv() -> __udp4_lib_rcv() -> __udp4_lib_lookup()
      在该函数中有关于udp socket的查找代码段,它以[saddr, sport, daddr, dport, iif]为键值在udptable中查找相应的sk。

return __udp4_lib_lookup(dev_net(skb_dst(skb)->dev), iph->saddr,sport,iph->daddr,dport,inet_iif(skb),udptable);

__udp4_lib_lookup() sock在udptable中查找
      查找的过程与插入sock的过程很相似,先以hnum作哈希得到hslot,daddr, hnum作哈希得到hslot2,如果hslot数目不足10或hslot的表项数少于hslot2的,则在hslot中查找(begin代码段)。否则,在hslot2中查找。查找时使用udp4_lib_lookup2()函数,它返回与收到报文相匹配的sock。

if (hslot->count > 10) {  hash2 = udp4_portaddr_hash(net, daddr, hnum);  slot2 = hash2 & udptable->mask;  hslot2 = &udptable->hash2[slot2];  if (hslot->count < hslot2->count)  goto begin;result = udp4_lib_lookup2(net,saddr, sport,daddr, hnum, dif, hslot2, slot2);

如果在hslot2中没有查找结果,则用INADDR_ANY, hnum作哈希得到重新得到hslot2,因为服务器端的udp socket只绑定了本地端口,没有绑定本地地址,所以查找时需要先使用[saddr, sport]查找,没有时再使用[INADDR_ANY, sport]查找。如果hslot2->count比hslot->count要多,或者在hslot2中没有查找到,则在hslot中查找(begin代码段)。

if (!result) {  hash2 = udp4_portaddr_hash(net, INADDR_ANY, hnum);  slot2 = hash2 & udptable->mask;  hslot2 = &udptable->hash2[slot2];  if (hslot->count < hslot2->count)  goto begin;  result = udp4_lib_lookup2(net, saddr, sport,INADDR_ANY, hnum, dif, hslot2, slot2);  } 

只有当不必或不能在hslot2中查找时,才会执行下面的查找,它在hslot中查找,遍历每一项,使用comute_score()计算匹配值。最后返回查找的结果。

[cpp] view plaincopy
  1. begin:
  2. result = NULL;
  3. badness = -1;
  4. sk_nulls_for_each_rcu(sk, node, &hslot->head) {
  5. score = compute_score(sk, net, saddr, hnum, sport,
  6. daddr, dport, dif);
  7. if (score > badness) {
  8. result = sk;
  9. badness = score;
  10. }
  11. }

流程图:

#对比udp socket的插入和查找的流程图,可以发现两者是有差别的,在使用INADDR_ANY作为本地地址重新计算hslot2后,前者并没有比较hslot2->count与hslot->count。虽然不碍查找结果,但个人认为,插入的流程是少了hslot2->count与hslot->count比较。

udp4_lib_lookup2()
      遍历hslot2的链表项,compute_score2计算与[saddr, sport, daddr, dport, dif]相匹配的表项,返回score作为匹配值,匹配值发越大表明匹配度越高。score==SCORE2_MAX表示与传入参数完全匹配,找到匹配项,goto exact_match;score==-1表示与传入参数完全不匹配;score==中间值表示部分匹配,如果没有更高的匹配项存在,则使用该项。

[cpp] view plaincopy
  1. udp_portaddr_for_each_entry_rcu(sk, node, &hslot2->head) {
  2. score = compute_score2(sk, net, saddr, sport, daddr, hnum, dif);
  3. if (score > badness) {
  4. result = sk;
  5. badness = score;
  6. if (score == SCORE2_MAX)
  7. goto exact_match;
  8. }
  9. }

其中compute_score2()用来计算匹配度,并用返回值作为匹配度,以通常的udp socket为例,只用到了本地地址、本地端口(如果是作为服务器,则本地地址也省略了)。因此compute_score2()要求本地地址和本地端口完全匹配,共余参数只要求当插入的socket有值时才进行匹配。

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

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

    内核版本:2.6.34 UDP报文接收        UDP报文的接收可以分为两个部分:协议栈收到udp报文,插入相应队列中:用户调用recvfrom()或recv()系统调用从队列中取出报文,这里的 ...

  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. python【力扣LeetCode算法题库】69-x 的平方根
  2. application/x-www-form-urlencoded与application/json区别以及遇到的坑
  3. 【OpenCV 例程200篇】85. 频率域高通滤波器的应用
  4. DCIC巡游车与网约车运营特征对比分析-数据读取
  5. Linux C语言在用户态实现一个低时延通知(eventfd)+轮询(无锁队列ring)机制的消息队列
  6. 【干货】从数字化洞察新消费趋势看数字化如何赋能企业.pdf(附下载链接)
  7. scrapy框架之分布式操作
  8. linux 卡在grub_关于linux开机进入grub问题的解决方法
  9. Angr安装与使用之使用篇(八)
  10. BAT批处理整人代码
  11. 【Ubuntu】如何使用命令行(优雅地)安装/卸载Microsoft Edge
  12. HTTP协议的详细介绍
  13. 解决Windows系统删除文件:文件正在使用,无法删除问题
  14. 西安计算机专业大专排名及分数线,西安所有的大学名单及排名分数线(本科 专科)...
  15. 0 -1 分布(两点分布)
  16. Steaming技术初体验
  17. Python爬虫51job职位
  18. 云帆文档管理系统版本更新说明:v4.6.0
  19. 3DTank大战总结
  20. [Editing] TP-LINK740N v5 firmware Crack

热门文章

  1. fat32转ntfs工具无损数据安全转换_干货真香! 无损制作UD三分区教程,新手小白的福利来了...
  2. python日期,从int格式为时间格式
  3. java正立三角形_java for循环练习(9*9乘法表、正三角形、菱形)
  4. 去哪儿网2018春招软件开发工程师、前段开发工程师编程题 - 题解
  5. HDU-5718 Oracle
  6. Frameworks.Entity.Core 1
  7. android UI开源库
  8. jQuery操作Table学习总结(转)
  9. [转] Tomcat 系统架构与设计模式,第 1 部分: 工作原理
  10. Linux 2.6.39.1 Hello world 驱动总结