您好,欢迎来到刀刀网。
搜索
您的当前位置:首页『 Linux 』文件与网络套接字的内部关系

『 Linux 』文件与网络套接字的内部关系

来源:刀刀网


回顾进程控制块

每个进程都存在着自己的PCB结构体,即task_struct结构体,这个结构体是用来描述一个进程的;

/* 已省略部分代码 */
struct task_struct {
	volatile long state;	/* Process state */
	void *stack;            /* Stack pointer */
	atomic_t usage;         /* Use count */
	unsigned int flags;	/* Per process flags */
	int prio, static_prio, normal_prio; /* Priority levels */
	unsigned int rt_priority;   /* Real-time priority */
	const struct sched_class *sched_class; /* Scheduling class */
	struct sched_entity se;     /* Scheduling entity for CFS */
	struct sched_rt_entity rt;  /* Scheduling entity for real-time tasks */
	unsigned char fpu_counter;  /* FPU usage counter */
	pid_t pid;                  /* Process ID */
	pid_t tgid;                 /* Thread group ID */
	struct mm_struct *mm, *active_mm; /* Memory management info */
	int exit_state;             /* State on exit */
	int exit_code, exit_signal; /* Exit code and signal */
	struct task_struct *real_parent; /* Real parent process */
	struct list_head children;   /* List of my children */
	struct list_head sibling;    /* Linkage in my parent's children list */
	char comm[TASK_COMM_LEN];    /* Task name */
	struct files_struct *files;  /* Open files info */
	cpumask_t cpus_allowed;      /* Allowed CPUs for this task */
	sigset_t blocked, real_blocked; /* Blocked signals */
	struct signal_struct *signal;/* Signal handlers */
	struct sighand_struct *sighand; /* Signal handling structure */
#ifdef CONFIG_CGROUPS
	struct css_set *cgroups;     /* Control groups info */
#endif
#ifdef CONFIG_FUTEX
	struct robust_list_head __user *robust_list; /* List of robust futexes */
#endif
#ifdef CONFIG_PERF_EVENTS
	struct perf_event_context *perf_event_ctxp; /* Performance event context */
#endif
	atomic_t fs_excl;            /* Holding fs exclusive resources */
};

在PCB结构体,即struct task_struct结构体中存在着一个指向struct files_struct结构体的指针,而在struct files_struct结构体中存在一个数组struct file * fd_array[NR_OPEN_DEFAULT]为文件描述符表;

struct files_struct {
  /*
   * read mostly part
   */
	atomic_t count;
	struct fdtable *fdt;
	struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
	spinlock_t file_lock ____cacheline_aligned_in_smp;
	int next_fd;
	struct embedded_fd_set close_on_exec_init;
	struct embedded_fd_set open_fds_init;
	struct file * fd_array[NR_OPEN_DEFAULT]; // 文件描述符表
};

其中该数组中对应的每个下标即为文件描述符,数组中的每个结构都象征着该进程所打开的各种文件;

struct file {
    struct path		    f_path;             // 文件路径
    const struct file_operations	*f_op; // 文件操作函数指针
    spinlock_t		    f_lock;            // 用于同步的自旋锁
    atomic_long_t	    f_count;           // 引用计数
    unsigned int 	    f_flags;           // 文件打开标志
    fmode_t			    f_mode;            // 文件模式
    loff_t			    f_pos;             // 当前文件位置(偏移量)
    struct fown_struct	f_owner;           // 文件所有者
    const struct cred	*f_cred;           // 安全凭证
    struct file_ra_state	f_ra;            // 预读状态信息

    void			    *private_data;     // 私有数据,供驱动程序使用

    struct address_space *f_mapping;        // 关联的地址空间对象
};

在该结构体中存在指针const struct file_operations *f_op用来存放一系列的文件操作函数;

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

而对应的磁盘文件的文件缓冲区则在struct file结构体中的struct address_space *f_mapping部分;


socket与文件的关系

struct file结构体中存在一个指针为void *private_data;

这个指针主要是用于私有数据,供驱动程序或其他文件使用;

而在创建网络套接字时所创建的大批数据结构中将会创建一个类型为struct socket{}的结构体对象;

struct socket {
	socket_state		state;

	kmemcheck_bitfield_begin(type);
	short			type;
	kmemcheck_bitfield_end(type);

	unsigned long		flags;
	/*
	 * Please keep fasync_list & wait fields in the same cache line
	 */
	struct fasync_struct	*fasync_list;
	wait_queue_head_t	wait;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};

对应的如果进程所打开的文件为网络文件,对应的void *private_data指针将会指向struct socket结构体对象;

而对应的struct socket结构体中的struct file *file指针将会回指向回文件描述符表struct file * fd_array[NR_OPEN_DEFAULT]数组中对应文件描述符指针所指向的struct file结构体对象;

如下图所示;

这个结构也印证了 “Linux下一切接文件” 的哲学;


wait_queue_head_t

struct socket结构中存在着一个 wait_queue_head_t wait;

其中这个wait是一个等待队列头部,通常用于管理在此套接字上可能阻塞等待的进程;

这个等待队列主要用于处理套接字的异步通知和阻塞操作,如:

  • 数据接收

    当进程试图从尚无数据的套接字读取数据时它可能会被放入这个等待队列并处于睡眠状态,直至有数据到达;

  • 连接过程

    对于某些类型的套接字(如TCP),在连接建立过程中,进程可能会在这个队列上阻塞,直至连接成功建立或发生错误;

  • 发送空间

    当发送缓冲区满时,试图发送数据的进程可能会在这个队列上休眠,直到缓冲区中有足够的空间进行数据发送;

假设使用浏览器在线观看视频,网速突然变慢或者是视频数据没有到达时,浏览器将希望从套接字收取更多的数据来显示视频,但由于数据尚未到达,浏览器操作数据的进程可能需要等待,对应的这个浏览器进程将会被添加到该套接字socket结构中的wait队列中进行等待;

当网络数据到达兵被操作系统处理后,相关部分的代码会检查有哪些进程在soket中的wait队列上等待,随后唤醒因等待数据到达而阻塞的进程;


文件与套接字相关的调用方法

struct socket结构体中存在一个指针 const struct proto_ops *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);
	int		(*getname)   (struct socket *sock,
				      struct sockaddr *addr,
				      int *sockaddr_len, int peer);
	unsigned int	(*poll)	     (struct file *file, struct socket *sock,
				      struct poll_table_struct *wait);
	int		(*ioctl)     (struct socket *sock, unsigned int cmd,
				      unsigned long arg);
	int	 	(*compat_ioctl) (struct socket *sock, unsigned int cmd,
				      unsigned long arg);
	int		(*listen)    (struct socket *sock, int len);
	int		(*shutdown)  (struct socket *sock, int flags);
	int		(*setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, unsigned int optlen);
	int		(*getsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int __user *optlen);
	int		(*compat_setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, unsigned int optlen);
	int		(*compat_getsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int __user *optlen);
	int		(*sendmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len);
	int		(*recvmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len,
				      int flags);
	int		(*mmap)	     (struct file *file, struct socket *sock,
				      struct vm_area_struct * vma);
	ssize_t		(*sendpage)  (struct socket *sock, struct page *page,
				      int offset, size_t size, int flags);
	ssize_t 	(*splice_read)(struct socket *sock,  loff_t *ppos,
				       struct pipe_inode_info *pipe, size_t len, unsigned int flags);
};

而在struct file结构体中也同样存在对应的方法集指针const struct file_operations *f_op;

这两个方法集对应着不同的上下文和目的;

其中const struct file_operations *f_op指针所指向的方法集中定义类文件操作的一系列接口方法,如打开open,读取read,写入write释放release等等;

const struct proto_ops *ops对应的方法集则是特定于网络协议套接字操作的方法,对应结构体中包含了一系列的函数指针,这些函数指针定义了网络套接字在不同协议栈(如TCP,UDP等)下的行为,包括套接字的创建create,连接connect,发送sendmsg,接收recvmsg等操作;


系统中的套接字

struct soket结构体中还有一个指针 struct sock *sk将会指向一个类型为struct sock的数据结构,而sock结构体即为网络的开始;

struct sock {
	// ...
	rwlock_t		sk_dst_lock;
	atomic_t		sk_rmem_alloc;
	atomic_t		sk_wmem_alloc;
	atomic_t		sk_omem_alloc;
	int			sk_sndbuf;
	struct sk_buff_head	sk_receive_queue;
	struct sk_buff_head	sk_write_queue;
	// ...
}

struct sock结构体中存在两个队列,分别为接收队列与发送队列,即该段代码中的sk_receive_queuesk_write_queue;

而在Linux内核中,采用了结构体间接嵌套的方式实现了类似继承的功能,其中struct sock结构体即为基类,对应的两个派生类的结构体分别为struct tcp_sockstruct udp_sock;

继承关系如下:

  • struct tcp_sock

    struct tcp_sock结构体中定义了一个struct inet_connection_sock结构体对象,继承于该结构体(类);

    struct tcp_sock {
    	/* inet_connection_sock has to be the first member of tcp_sock */
    	struct inet_connection_sock	inet_conn;
        // ...
    }
    

    struct inet_connection_sock中包含一个struct inet_sock类型的结构体变量,继承于该结构体(类);

    struct inet_connection_sock {
    	/* inet_sock has to be the first member! */
    	struct inet_sock	  icsk_inet;
        // ...
    }
    

    而实际struct inet_sock结构体中才包含了一个struct sock类型对象,struct tcp_sock结构体类型间接继承struct sock;

    struct inet_sock {
    	/* sk and pinet6 has to be the first two members of inet_sock */
    	struct sock		sk;
    	// ...
    }
    

  • struct udp_sock

    struct tcp_sock结构不同,struct udp_sock较其要少一层嵌套;

    struct udp_sock结构体中存在一个struct inet_sock类型对象,这表示着struct udp_sock直接继承于struct inet_sock(往前的继承参考struct tcp_sock类型);

对应的继承图即为:

通常应用层在创建对应的套接字时创建的为TCPsocket或者是UDPsocket;

而在调用socket()时所传的参数SOCK_STREAMSOCK_DGRAM则为不同协议的选择;

当选择到对应的选项时将自动实例化一个struct tcp_sock对象或者是struct udp_sock对象;

而对应的struct socket结构体中的struct sock *sk指针将根据选项指向对应的struct tcp_sock对象或者是struct udp_sock对象;

而系统中的struct socket类型中的一些成员属性将辨别所使用的套接字属于TCP还是UDP,当系统需要访问实际套接字中的某些成员属性时只需要进行强制类型转换即可;

即以C语言的方式实现多态;

当进行收发数据时只需要使用struct sock结构中的sk_receive_queuesk_write_queue即可;


网络协议栈与方法集

以TCP/IP四层协议模型为例,分别为应用层,传输层,网络层与数据链路层;

而协议栈的本质就是:

  • 用特定数据结构表述的协议的约定
  • 和特定协议匹配的方法集

在上文中提到了两种方法集,分别为struct file结构体中的const struct file_operations *f_op方法集与struct socket结构体中的 const struct proto_ops *ops方法集;

当对应的struct file中不表示指向磁盘文件而是网络套接字时,对应的const struct file_operations *f_op方法集也不再指向磁盘文件的操作方法,而是指向网络相关的操作方法;

struct socket结构体中的 const struct proto_ops *ops方法集指向的也是网络相关的操作方法;

通常情况下f_op方法集在文件指向网络时,主要的操作作用域是对上的,通常情况下在进行网络通信时,数据的生产位置是位于用户层/应用层的,而f_op方法集则是在这时候通过一些接口将数据从用户层/应用层拷贝至内核层;

而方法集ops则负责将数据由内核发送至网络或者是发送队列中(对UDP而言将直接发送给网络传送给对端,TCP则是需要拷贝至发送队列中);


报文的管理

在任何一端机器,无论是发送方还是接收方,注定系统中将存在大量的报文,而报文同样是需要被管理的,管理的方式同样采用 “先描述再组织” 的方式;

在上文中提到了对应的发送队列与接收队列,即struct sock结构中的sk_receive_queuesk_write_queue,其对应的类型都为struct sk_buff_head;

	struct sk_buff_head	sk_receive_queue;
	struct sk_buff_head	sk_write_queue;

该类型的定义为:

struct sk_buff_head {
	/* These two members must be first. */
	struct sk_buff	*next;
	struct sk_buff	*prev;

	__u32		qlen;
	spinlock_t	lock;
};

这个类型实际上是发送/接收队列的头部,在这个结构体中存在两个指针,struct sk_buff *nextstruct sk_buff *prev,以双链表的形式将报文进行管理,而实际上struct sk_buff则是用来描述报文的类型;

struct sk_buff {
    	/* These two members must be first. */
    struct sk_buff *next;
    struct sk_buff *prev;
    struct sock *sk;               // Associated socket
    struct net_device *dev;        // Associated network device

    unsigned int len;              // Total length of data
    unsigned int data_len;         // Payload length
    char cb[48];                   // Control buffer for private use

    __u32 priority;                // Packet priority
    __u16 protocol;                // Protocol (e.g., ETH_P_IP, ETH_P_ARP)

    // ...
    
    void (*destructor)(struct sk_buff *skb); // Destructor function

    // ...
    
	__u32			mark;

	__u16			vlan_tci;

	sk_buff_data_t		transport_header;
	sk_buff_data_t		network_header;
	sk_buff_data_t		mac_header;
	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;
	sk_buff_data_t		end;
	unsigned char		*head,
				*data;
	unsigned int		truesize;
	atomic_t		users;
};

通常情况下在操作系统中,报文为了确保数据完整都是一块较为连续的问题,而为了能够确保清楚报文在物理内存中的具体位置,struct sk_buff中存在各种指针来维护与区别报文的具体位置;

为了整个报文在协议栈中层与层之间进行流动,采用统一的方式对报文进行描述组织,每一层在进行封装与解包时只需要移动指针即可;

同时因为接收和发送的动作本质上就是一种生产者和消费者模型,即需要在队列中放置数据或者读取数据,所以在该类型中定义了一把锁,spinlock_t lock;

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- gamedaodao.com 版权所有 湘ICP备2022005869号-6

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务