Linux网络解读(1) - 设备初始化

简介: 设备初始化

代码是基于2.6.18,挑了几个比较重要的函数和2.6.32做了对比,发现核在网络上代码并没有太的变化。

网络的初始化

网络的初始化分4个阶段:socket_init,inet_init,net_dev_init,驱动的初始化。

依次解读这个4个阶段的初始化工作,在这之前要先找到这4个函数的调用入口。

这4个阶段的调用发生在:

start_kernel() -> rest_init() -> kernel_thread:init() -> do_basic_setup() -> do_initcalls()

在rest_init里面会启动一个内核线程,这个线程进入init函数,最终在do_initcalls里面调用到了所有被__init修饰的函数。

do_initcalls

static void __init do_initcalls(void)
{
    initcall_t *call;
    int count = preempt_count();
    for (call = __initcall_start; call < __initcall_end; call++) {
        result = (*call)();
    }
}

__initcall_start和__initcall_end看起来是函数指针的数组,他们是在哪里设置的呢?

这两个符号是链接器在链接阶段动态产生的,并非是写在代码里的。这个过程是链接脚本控制的。

内核的链接脚本

ld -verbose

显示系统默认的链接脚本。

而,内核有自己的链接脚本,在源码的arch/x86_64/kernel/vmlinux.lds.S

__initcall_start = .;
.initcall.init : {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;

其中initcall1.init第1优先级得到执行,initcall7为第7优先级得到执行,其定义如下:

#define core_initcall(fn)__define_initcall("1",fn)
#define postcore_initcall(fn)__define_initcall("2",fn)
#define arch_initcall(fn)__define_initcall("3",fn)
#define subsys_initcall(fn)__define_initcall("4",fn)
#define fs_initcall(fn)__define_initcall("5",fn)
#define device_initcall(fn)__define_initcall("6",fn)
#define late_initcall(fn)__define_initcall("7",fn)
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn

可以看到,core_initcall等修饰的函数,其实就是给这个函数添加__section__属性。

而链接器脚本把所有__initcallN修饰的函数放在_initcall_start开始, initcall_end结束的数组里。

通过objump可以查看编译后的vmlinux中的符号。

objdump  -t vmlinux |grep initcall
ffffffff817aedd0 g       .init.data     0000000000000000 __initcall_start
ffffffff817af8c0 g       .init.data     0000000000000000 __initcall_end

第一阶段:socket_init 基础设施的初始化

代码文件:net/socket.c

static int __init sock_init(void)
{
   sk_init();
   skb_init();
   init_inodecache();
   register_filesystem(&sock_fs_type);
   sock_mnt = kern_mount(&sock_fs_type);
   netfilter_init();
   return 0;
}

1) sk_init

初始化socket的SLABcache。

2) skb_init

初始化skbuff的SLABcache。

3) init_inodecache

初始化套接字和文件系统的接口。

static int init_inodecache(void)
   {
       sock_inode_cachep = kmem_cache_create("sock_inode_cache",
                                             sizeof(struct socket_alloc),
                                             0, (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
                                             SLAB_MEM_SPREAD),
                                             init_once, NULL);
   }

sock_inode_cache在内核中存储socket的内核结构。创建对象池sock_inode_cachep用来分配socket_alloc。

4) register_filesystem(&sock_fs_type);

sock_mnt = kern_mount(&sock_fs_type);

在全局变量file_systems等级scok类型的文件系统。

其中

static struct file_system_type sock_fs_type = {
   .name ="sockfs",
   .get_sb =sockfs_get_sb,
   .kill_sb =kill_anon_super,
   };

经过这两个函数调用,最终把sockfs挂入了VFS中。

sock_fs_type 挂入了全局变量 file_systems中;

通过sockfs_get_sb()生成的super_block 挂入了全局变量 super_block中。

形成了三元组:<super_block, sock_fs_type, sockfs_ops>

skbuff

网络协议栈最重要的数据结构,skbuff承载要发送,接收的数据,经过tcp层,ip层,Ethernet层。
所以,skbuff结构体中有各层协议头部的结构体:
struct sk_buff {
    /* These two members must be first. */
    struct sk_buff*next;
    struct sk_buff*prev;
    struct sock*sk;
    struct skb_timevaltstamp;
    struct net_device*dev;
    struct net_device*input_dev;
    union {
    struct tcphdr*th;
    struct udphdr*uh;
    struct icmphdr*icmph;
    struct igmphdr*igmph;
    struct iphdr*ipiph;
    struct ipv6hdr*ipv6h;
    unsigned char*raw;
    } h;
    union {
    struct iphdr*iph;
    struct ipv6hdr*ipv6h;
    struct arphdr*arph;
    unsigned char*raw;
    } nh;
    union {
    unsigned char *raw;
    } mac;
    struct  dst_entry*dst;
    structsec_path*sp;
     /* These elements must be at the end, see alloc_skb() for details.  */
     unsigned inttruesize;
     atomic_tusers;
     unsigned char*head,
     *data,
     *tail,
     *end;
     };

image.png

为了加速skbuff的分配,在SLAB系统有两个cachep:skbuff_head_cache和skbuff_fclone_cache。

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int fclone)
{
    kmem_cache_t *cache;
    struct skb_shared_info *shinfo;
    struct sk_buff *skb;
    u8 *data;
    cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
    // 分配sk_buff结构体
    skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);
    if (!skb)
    goto out;
    size = SKB_DATA_ALIGN(size);
    // kmalloc分配数据体
    data = ____kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
    if (!data)
    goto nodata;
    memset(skb, 0, offsetof(struct sk_buff, truesize));
    skb->truesize = size + sizeof(struct sk_buff);
    atomic_set(&skb->users, 1);
    // 设置head, data, tail, end指向数据体的开始
    skb->head = data;
    skb->data = data;
    skb->tail = data;
    skb->end  = data + size;
    // 在sk_buff尾部还有一个结构体描述聚合IO信息
    shinfo = skb_shinfo(skb);
    atomic_set(&shinfo->dataref, 1);
    shinfo->nr_frags  = 0;
    shinfo->gso_size = 0;
    shinfo->gso_segs = 0;
    shinfo->gso_type = 0;
    shinfo->ip6_frag_id = 0;
    shinfo->frag_list = NULL;
    // 如果是clone, 则设置相邻的sk_buff
    if (fclone) {
        struct sk_buff *child = skb + 1;
        atomic_t *fclone_ref = (atomic_t *) (child + 1);
        skb->fclone = SKB_FCLONE_ORIG;
        atomic_set(fclone_ref, 1);
        child->fclone = SKB_FCLONE_UNAVAILABLE;
}

各层协议会把该层协议的头部信息append到head, data, tail, end的指针指向的内存。

image.png

第二阶段:inet_init 网络协议的初始化

inet_init()的初始化非常重要,主要初始化了tcp/ip协议栈中各个层支持的协议族,协议类型,协议对应的操作。

用户态创建套接字的过程就是根据套接字的参数,从这些基础设施中选择出对应的协议。

初始化3个全局的数据结构:

1) net_families[];

2) inetsw_array;

3) inet_protos.

image.png

地址族和套接字类型

地址族

地址族的结构体:

struct net_proto_family {
    int family;
    int(*create)(struct socket *sock, int protocol);
    short authentication;
    short encryption;
    short encrypt_net;
    struct module*owner;
   };
}

系统预留了32种地址族。已经使用了PF_UNIX, PF_INET, PF_NETLINK, PF_PACKET。

static struct net_proto_family *net_families[NPROTO];

inet_init

static int __init inet_init(void)
{
    rc = proto_register(&tcp_prot, 1);
    rc = proto_register(&udp_prot, 1);
    rc = proto_register(&raw_prot, 1);
    (void)sock_register(&inet_family_ops);
    inet_add_protocol(&icmp_protocol, IPPROTO_ICMP);
    inet_add_protocol(&udp_protocol, IPPROTO_UDP);
    inet_add_protocol(&tcp_protocol, IPPROTO_TCP);
    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);
    arp_init();
    ip_init();
    tcp_init();
    icmp_init(&inet_family_ops);
    dev_add_pack(&ip_packet_type);
}

1) 调用3次proto_register,将tcp_prot, udp_prot, raw_prot挂入到全局变量prot_list上.

struct proto {
    void(*close)(struct sock *sk, long timeout);
    int(*connect)(struct sock *sk, struct sockaddr *uaddr, int addr_len);
    int(*ioctl)(struct sock *sk, int cmd, unsigned long arg);
    int(*sendmsg)(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len);
    int(*setsockopt)(struct sock *sk, int level, int optname, char __user *optval, int optlen);
    int(*bind)(struct sock *sk, struct sockaddr *uaddr, int addr_len);
    ...
    int *memory_pressure;
    int *sysctl_mem;
    int *sysctl_wmem;
    int *sysctl_rmem;
    kmem_cache_t *slab;
    unsigned int obj_size;
}

2) sock_register

把inet_family_ops添加到net_families数组里。

unix_family_ops和netlink_family_ops也是通过这个函数添加进来的。

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

3) inet_add_protocol

注册接收函数到全局数组inet_protos

static struct net_protocol tcp_protocol = {
    .handler =tcp_v4_rcv,
    .err_handler =tcp_v4_err,
    .gso_send_check = tcp_v4_gso_send_check,
    .gso_segment =tcp_tso_segment,
    .no_policy =1,
};
static struct net_protocol udp_protocol = {
    .handler =udp_rcv,
    .err_handler =udp_err,
    .no_policy =1,
};
static struct net_protocol icmp_protocol = {
    .handler =icmp_rcv,
};

4) inet_register_protosw(q);

注册协议切换表到全局变量inetsw中

static struct inet_protosw inetsw_array[] =
{
        {
            .type =       SOCK_STREAM,
            .protocol =   IPPROTO_TCP,
            .prot =       &tcp_prot,
            .ops =        &inet_stream_ops,
            .capability = -1,
            .no_check =   0,
            .flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
        },
        {
            .type =       SOCK_DGRAM,
            .protocol =   IPPROTO_UDP,
            .prot =       &udp_prot,
            .ops =        &inet_dgram_ops,
            .capability = -1,
            .no_check =   UDP_CSUM_DEFAULT,
            .flags =      INET_PROTOSW_PERMANENT,
        },
        {
            .type =       SOCK_RAW,
            .protocol =   IPPROTO_IP,/* wild card *
            .prot =       &raw_prot,
            .ops =        &inet_sockraw_ops,
            .capability = CAP_NET_RAW,
            .no_check =   UDP_CSUM_DEFAULT,
            .flags =      INET_PROTOSW_REUSE,
        }
}

5) arp_init();

ip_init();

tcp层以下的协议层,需要一个接收函数用来解封装报文。

对底层协议感兴趣的协议有两个:arp和ip。

报文从设备送往上层之前必须区分是arp报文还是ip报文。

这个过程由packet_type中的type来完成。

struct packet_type {
    __be16 type;/* This is really htons(ether_type). */
    struct net_device* dev;/* NULL is wildcarded here     */
    int(*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);
    ...
};

6) dev_add_pack(&ip_packet_type);

添加ip的handler到协议栈ptype_base中。

ip_packet_type的定义如下:

static struct packet_type ip_packet_type = {
    .type = __constant_htons(ETH_P_IP),
    .func = ip_rcv,
    .gso_send_check = inet_gso_send_check,
    .gso_segment = inet_gso_segment,
};

第三阶段:net_dev_init 抽象网络设备的初始化

static int __init net_dev_init(void)
{
    net_random_init();
    dev_proc_init();
    netdev_sysfs_init();
    for_each_possible_cpu(i) {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        skb_queue_head_init(&queue->input_pkt_queue);
        queue->completion_queue = NULL;
        INIT_LIST_HEAD(&queue->poll_list);
        set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
        queue->backlog_dev.weight = weight_p;
        queue->backlog_dev.poll = process_backlog;
        atomic_set(&queue->backlog_dev.refcnt, 1);
    }
    open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
}

1) for_eache_possible_cpu

每个cpu都有一个队列用来收发数据包,因此不同cpu之间无需上锁。

全局变量 per_cpu_softnet_data[NCPU]就是这个数组。

struct softnet_data
{
    struct net_device*output_queue;
    struct sk_buff_headinput_pkt_queue;
    struct list_headpoll_list;
    struct sk_buff*completion_queue;
    struct net_devicebacklog_dev;/* Sorry. 8) */
    #ifdef CONFIG_NET_DMA
        struct dma_chan*net_dma;
    #endif
};

1) open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);

open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

设置软中断!

第四阶段:设备驱动的初始化

PCI总线会调用pci_module_init 初始化驱动,

__driver_attach() -> driver_probe_device() -> ret = drv->probe(dev);

void driver_attach(struct device_driver * drv)
{
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
    int ret = 0;
    if (drv->bus->match && !drv->bus->match(dev, drv))
        goto Done;
     dev->driver = drv;
     if (dev->bus->probe) {
         ret = dev->bus->probe(dev);
         if (ret) {
             dev->driver = NULL;
             goto ProbeFailed;
         }
     } else if (drv->probe) {
         ret = drv->probe(dev);
         if (ret) {
             dev->driver = NULL;
             goto ProbeFailed;
         }
     }
     device_bind_driver(dev);
     ret = 1;
}

遍历所有的device,如果drv->bus->match(dev, drv),当前的驱动和设备match了,

会通过drv->probe()最终进入具体的驱动程序初始化。

int register_netdevice(struct net_device *dev)
{
    dev->ifindex = dev_new_index();
    head = dev_name_hash(dev->name);
    ret = netdev_register_sysfs(dev);
    *dev_tail = dev;
    dev_tail = &dev->next;
    hlist_add_head(&dev->name_hlist, head);
    hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex));
}

1) *dev_tail = dev;

dev_tail = &dev->next;

把dev添加到dev_base列表中。

2) hlist_add_head(&dev->name_hlist, head)

根据dev->name,把dev添加到dev_name_head哈希表中。

3) hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex));

根据dev->ifindex,把dev添加到dev_index_head哈希表中。

4) net_device的结构体很庞大,只列出比较重要的。

有几个网卡接口就会有几个这样的数据结构。

struct net_device
{
    unsigned long base_addr;/* device I/O address*/
    unsigned int irq;/* device IRQ number*/
    void *ip_ptr;
    void *dev;
    // Qos相关
    struct Qdisc *qdisc;
    struct Qdisc* qdisc_sleeping;
    struct list_headqdisc_list;
    Unsigned long tx_queue_len;
    struct hlist_nodeindex_hlist;
}

image.png

in_device和net_device之间的关系:

1) in_device{}: 设备无关层,保存IP地址信息,neighbour信息;

2) net_device{}: 保存设备的名字,地址,等共性;

3) 特定的网络层驱动,有硬件发送缓冲,接收缓冲,芯片的寄存器。这片内存一般是紧紧的跟在net_device之后。

loopback的初始化

文件:drivers/net/loopback.c

首先是loopback的net_device结构体

struct net_device loopback_dev = {
    .name = "lo",
    .mtu= (16 * 1024) + 20 + 20 + 12,
    .hard_start_xmit= loopback_xmit,
    .hard_header= eth_header,
    .hard_header_cache= eth_header_cache,
    .header_cache_update= eth_header_cache_update,
    .hard_header_len= ETH_HLEN,/* 14*/
    .addr_len= ETH_ALEN,/* 6*/
    .tx_queue_len= 0,
    .type= ARPHRD_LOOPBACK,/* 0x0001*/
    .rebuild_header= eth_rebuild_header,
    .flags= IFF_LOOPBACK,
    .features = NETIF_F_SG | NETIF_F_FRAGLIST
    .ethtool_ops= &loopback_ethtool_ops,
}

loopback的初始化并非是有PCI调用的,而是在系统初始化时主动把自己初始化的:

int __init loopback_init(void)
{
    struct net_device_stats *stats;
    stats = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL);
    memset(stats, 0, sizeof(struct net_device_stats));
    loopback_dev.priv = stats;
    loopback_dev.get_stats = &get_stats;
    return register_netdev(&loopback_dev);
}
相关文章
|
1月前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
74 2
|
14天前
|
5G 数据安全/隐私保护
如果已经链接了5Gwifi网络设备是否还能搜索到其他5Gwifi网络
当设备已经连接到一个5G Wi-Fi网络时,它仍然有能力搜索和发现其他可用的5G Wi-Fi网络。这里所说的“5G Wi-Fi”通常指的是运行在5GHz频段的Wi-Fi网络,而不是与移动通信中的5G网络(即第五代移动通信技术)混淆。
|
27天前
|
网络协议 安全 文件存储
动态DNS(DDNS)技术在当前网络环境中日益重要,它允许使用动态IP地址的设备通过固定域名访问
动态DNS(DDNS)技术在当前网络环境中日益重要,它允许使用动态IP地址的设备通过固定域名访问,即使IP地址变化,也能通过DDNS服务保持连接。适用于家庭网络远程访问设备及企业临时或移动设备管理,提供便捷性和灵活性。示例代码展示了如何使用Python实现基本的DDNS更新。尽管存在服务可靠性和安全性挑战,DDNS仍极大提升了网络资源的利用效率。
48 6
|
26天前
|
存储 网络性能优化 网络虚拟化
局域网络设备
网卡、中继器、集线器、网桥和交换机是网络通信中的关键设备。网卡实现计算机与网络的连接,中继器用于延长网络传输距离,集线器将多台设备连接至共享网络,网桥通过MAC地址转发数据,而交换机提供高性能的数据转发和过滤服务,支持VLAN、QoS等功能,适用于不同规模的网络环境。
37 3
|
1月前
|
域名解析 网络协议 安全
|
1月前
|
网络协议 安全 网络安全
|
2月前
|
运维 监控 网络协议
|
2月前
|
存储 网络安全 数据安全/隐私保护
|
1月前
|
网络虚拟化 数据安全/隐私保护 数据中心
对比了思科和华为网络设备的基本配置、接口配置、VLAN配置、路由配置、访问控制列表配置及其他重要命令
本文对比了思科和华为网络设备的基本配置、接口配置、VLAN配置、路由配置、访问控制列表配置及其他重要命令,帮助网络工程师更好地理解和使用这两个品牌的产品。通过详细对比,展示了两者的相似之处和差异,强调了持续学习的重要性。
54 2
|
1月前
|
存储 Ubuntu Linux
2024全网最全面及最新且最为详细的网络安全技巧 (三) 之 linux提权各类技巧 上集
在本节实验中,我们学习了 Linux 系统登录认证的过程,文件的意义,并通过做实验的方式对 Linux 系统 passwd 文件提权方法有了深入的理解。祝你在接下来的技巧课程中学习愉快,学有所获~和文件是 Linux 系统登录认证的关键文件,如果系统运维人员对shadow或shadow文件的内容或权限配置有误,则可以被利用来进行系统提权。上一章中,我们已经学习了文件的提权方法, 在本章节中,我们将学习如何利用来完成系统提权。在本节实验中,我们学习了。