文章目录
- 回顾进程控制块
- socket与文件的关系
- wait_queue_head_t
- 文件与套接字相关的调用方法
- 系统中的套接字
- 网络协议栈与方法集
- 报文的管理
回顾进程控制块
每个进程都存在着自己的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_CGROUPSstruct css_set *cgroups; /* Control groups info */
#endif
#ifdef CONFIG_FUTEXstruct robust_list_head __user *robust_list; /* List of robust futexes */
#endif
#ifdef CONFIG_PERF_EVENTSstruct perf_event_context *perf_event_ctxp; /* Performance event context */
#endifatomic_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_queue
和sk_write_queue
;
而在Linux内核中,采用了结构体间接嵌套的方式实现了类似继承的功能,其中struct sock
结构体即为基类,对应的两个派生类的结构体分别为struct tcp_sock
与struct 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_STREAM
与SOCK_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_queue
和sk_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_queue
和sk_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 *next
与struct 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 socketstruct net_device *dev; // Associated network deviceunsigned int len; // Total length of dataunsigned int data_len; // Payload lengthchar 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
;