文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【计算机网络】Linux 内核网络概述

2023-10-04 22:16

关注

 文章目的

概述

        网络应用程序的开发过去这些年按照指数级增长,这样增加了对系统网络子系统的速度要求和产品化要求。网络子系统不是 Linux 内核必须的组件(Linux 内核可以在没有网络支持的情况下编译通过)。然而非常少的计算系统(即便是嵌入式设备)很难没有网络支持,因为它们都需要联网。现代操作系统使用 TCP/IP 协议栈,协议栈实现了传输层以下的所有协议层,应用层协议通常在用户空间实现(HTTP、FTP、SSH等)。

用户空间网络

        用户空间中,网络网络通信被抽象为套接字(socket),套接字抽象了通信通道,是基于内核 TCP/IP 协议栈交互接口。一个 IP 套接字和一个 IP 地址、传输层协议(TCP、UDP 等)、以及一个端口关联。使用套接字的普通函数调用包括:创建(socket)、初始化(bind)、连接(connect)、关闭套接字(close)。

        TCP 套接字通过 read/write 或者 recv/send 调用完成网络通信,而 UDP 使用 recvfrom/sento 接口调用完成网络调用。通信过程中的传输和接收操作对于应用程序来讲是透明的,即数据的封装以及网络传输由内核决定。然而,也可以通过原始套接字在用户空间实现 TCP/IP 协议栈(传教套接字时使用 PF_PACKET 选项),或者在内核实现应用层协议(比如 TUX web server)。

        更多关于用户空间使用套接字编程的信息,参考 Beej's Guide to Network Programming 

Linux 网络通信

        Linux 内核提供了网络包工作的三个基本数据结构:struct socket、struct sock、struct sk_buff。

        前两个是套接字的抽象:

        这两个结构是有关联的:struct socket 包含 INET 套接字字段,struct sock 有一个 BSD 套接字包含它。

        struct sk_buff 结构是网络包及其状态的表示。当内核接到一个一个数据包时,就会创建这个结构,数据包可以是从用户空间传来的也可以是从网络接口传来的。

struct socket 结构

        struct socket 结构是 BSD 套接字在内核的表示,在它上面执行的操作和内核提供的操作非常类似(通过系统调用)。一些套接字的常见操作(创建、初始化/绑定、关闭等)会导致特定的系统调用,这些系统调用会使用 struct socket 结构。

        struct socket 操作在 net/socket.c 中实现,它是和具体协议类型无关的。因此,struct socket 结构是一个在各种网络操作实现上的一个通用接口。通常,这些操作以 sock_ 前缀开始。

socket 结构上的操作

creation

创建操作和用户空间调用 socket() 函数类似,但是创建出的套接字被存到了 res 参数中:

这些调用的如下:

        在内核空间创建 TCP 套接字,必须调用:

struct socket *sock;int err;err = sock_create_kern(&init_net, PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);if (err < 0) {        }

        创建一个 UDP 套接字:

struct socket *sock;int err;err = sock_create_kern(&init_net, PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock);if (err < 0) {        }

         sys_socket() 系统调用处理函数(handler)中有相关使用举例:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol){      int retval;      struct socket *sock;      int flags;            BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);      BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);      BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);      BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);      flags = type & ~SOCK_TYPE_MASK;      if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))              return -EINVAL;      type &= SOCK_TYPE_MASK;      if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))              flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;      retval = sock_create(family, type, protocol, &sock);      if (retval < 0)              goto out;      return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));}

closing

        关闭连接(面向连接的套接字)并释放相关资源:

void sock_release(struct socket *sock){      if (sock->ops) {              struct module *owner = sock->ops->owner;              sock->ops->release(sock);              sock->ops = NULL;              module_put(owner);      }      //...}

sending/receiving 消息

        使用下面函数进行消息发送和接收:

int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags);int kernel_recvmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size, int flags);int sock_sendmsg(struct socket *sock, struct msghdr *msg);int kernel_sendmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size);

        消息发送和接收函数会调用套接字 ops 字段的 sendmsg、recvmsg 函数,具有 kernel_ 前缀的函数是套接字在内核中使用的。

        参数如下:

        sys_sendto() 系统调用处理函数里有一些使用例程:

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,              unsigned int, flags, struct sockaddr __user *, addr,              int, addr_len){      struct socket *sock;      struct sockaddr_storage address;      int err;      struct msghdr msg;      struct iovec iov;      int fput_needed;      err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);      if (unlikely(err))              return err;      sock = sockfd_lookup_light(fd, &err, &fput_needed);      if (!sock)              goto out;      msg.msg_name = NULL;      msg.msg_control = NULL;      msg.msg_controllen = 0;      msg.msg_namelen = 0;      if (addr) {              err = move_addr_to_kernel(addr, addr_len, &address);              if (err < 0)                      goto out_put;              msg.msg_name = (struct sockaddr *)&address;              msg.msg_namelen = addr_len;      }      if (sock->file->f_flags & O_NONBLOCK)              flags |= MSG_DONTWAIT;      msg.msg_flags = flags;      err = sock_sendmsg(sock, &msg);out_put:      fput_light(sock->file, fput_needed);out:      return err;}

        struct socket 的字段:

struct socket {      socket_state            state;      short                   type;      unsigned long           flags;      struct socket_wq __rcu  *wq;      struct file             *file;      struct sock             *sk;      const struct proto_ops  *ops;};

        需要解释的字段有:

struct proto_ops 结构

        struct proto_ops 结构包含特定操作的实现(TCP/UDP 等),这些函数会被 struct socket(sock_release(), sock_sendmsg() 等) 普通函数调用。

        struct proto_ops 结构也就包含了一些指向这些协议实现的指针:

struct proto_ops {      int             family;      struct module   *owner;      int             (*release)   (struct socket *sock);      int             (*bind)      (struct socket *sock,        struct sockaddr *myaddr,        int sockaddr_len);      int             (*connect)   (struct socket *sock,        struct sockaddr *vaddr,        int sockaddr_len, int flags);      int             (*socketpair)(struct socket *sock1,        struct socket *sock2);      int             (*accept)    (struct socket *sock,        struct socket *newsock, int flags, bool kern);      int             (*getname)   (struct socket *sock,        struct sockaddr *addr,        int peer);      //...}

        struct socket 的 ops 字段的初始化是通过 __sock_create() 函数实现的,通过调用 create() 函数,指定每个协议。一个等效的调用是 __sock_create() 函数的实现:

//...      err = pf->create(net, sock, protocol, kern);      if (err < 0)              goto out_module_put;//...

        这个会初始化这些函数指针为套接字指定协议类型的函数,sock_register() 和 sock_unregister() 调用用来填充 net_fanilies 向量。

        对于剩余的 socket 操作(除了创建、关闭、发送接收外),也会通过指针来调用,比如 bind 函数:

#define MY_PORT 60000struct sockaddr_in addr = {      .sin_family = AF_INET,      .sin_port = htons (MY_PORT),      .sin_addr = { htonl (INADDR_LOOPBACK) }};//...      err = sock->ops->bind (sock, (struct sockaddr *) &addr, sizeof(addr));      if (err < 0) {                    }//...

来源地址:https://blog.csdn.net/BillyThe/article/details/133430388

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯