1,interrupt.h
在该中断头文件中定义了结构体struct bh_struct:
struct bh_struct{
void (*routine)(void *); ------------软中断处理函数
void *data; ------------软中断数据
};
也定义了使能位,long bh_active , bh_mask;
另外也定一个了软中断需要处理的数组:
struct bh_struct bh_base[32];
目前只有8个软中断类型需要处理,这8个软中断类型用枚举定义如下:
enum{
TIMER_BH = 0 , ------------定时器软中断
CONSOLE_BH ,
TQUEUE_BH ,
SERIAL_BH ,
NET_BH -----------网络软中断
在kernel/softirq.c中定义了do_bottom_half()函数,该函数被定期执行,每次执行都会看看bh_base中有没有被使能的软中断,如果有的话就会调用bh_struct.routine(bh_struct.data)来处理
2,net.h
该头文件定义了socket核心结构体,结构体如下:
struct socket{
short type ; ----------socket类型; SOCK_DGRAM(UDP数据报) , SOCK_STREAM(TCP数据流)
socket_state state ; ---------socket状态
long flags;
struct proto_ops *ops; ---------该socket可以进行操作的函数指针结构体
void *data;
struct socket *next; ---------socket组成链表的指针
struct wait_queue **wait; --------等待在该socket上的阻赛队列
struct inode *inode ; --------- 该socket所隶属的索引节点,其实socket就是inode结构体中的一个结构体
struct fasync_struct *fasync_list ; ----------异步通知队列
};
socket_state表示socket的状态,socket有如下几个状态:
SS_FREE -------该socket还未分配
SS_UNCONNECTED -------该socket还未有任何连接
SS_CONNECTING -------该socket正在连接中
SS_CONNECTED -------该socket已经被连接
SS_DISCONNECTING -------该 socket正在断开连接
proto_ops结构体定义了可以对一个socket进行操作的函数集合,这些函数有:
proto_ops结构体中函数集合 | |
函数定义 | 函数备注 |
int (*create)(struct socket*sock , int protocol) | |
int (*dup)(struct socket *newsock , socket *oldsock ) | |
int (*release)(strcut socket *sock , struct *peer) | |
int (*bind)(struct socket *sock , struct socketaddr *umyaddr, int sockaddr_len) | 将sock绑定到地址umyaddr上去,绑定到底是什么意思 |
int (*connect)(struct socket *sock , struct socketaddr *umyaddr , int socketaddr_len,int flags) | 将本地sock连接到远程地址umyaddr上面去 |
int (*accept)(struct socket *sock , struct socket *newsock , int flags) | 从sock的已建立连接队列中提取一个已经建立好的连接,并给这个连接分配一个新的套接字,新套接字的指针为newsock |
int (*getname)(struct socket*sock , struct sockaddr *uaddr , int *sockaddr_len , int peer) | |
int (*read)(struct socket*sock , char *ubuf , int size , int nonblock) | 从sock中读取size个字节的数据到用户地址空间ubuf处,sock开始读取的起始位置为哪里? |
int (*write)(struct socket *sock , char *ubuf , int size , int nonblock) | 从用户地址空间ubuf处开始写入size个字节到socket中,写入套接字的起始地址在哪里? |
int (*listen)(struct socket *sock , int len) | 服务器套接字监听客户端的连接请求,len指定内核需要为套接字保持的队列长度 |
int (*send)(struct socket *sock , void *buff , int len , int nonblock , int flags) | 从用户空间buff处开始向套接字socket写入len个字节的数据,写入套接字的起始地址在哪里? |
int (*recv)(struct socket *sock , void *buff , int len , int nonblock , int flags) | 从socket中读取len个字节的数据到用户空间buff处,sock开始读取的起始位置在哪里? |
int (*shutdown)(struct socket *sock , int flags) | 该函数只用来关闭本地套接字到远程套接字的连接,注意该函数只是不让客户端向服务端发送数据,但是依然可以接收服务端传送过来的数据,所以该函数仅仅是修改一下标志位 |
该头文件还声明了几个函数,如下所示:
net.h声明的函数 | |
函数名 | 函数备注 |
int sock_register(int family , struct proto_ops *ops) | 往内核中注册一个协议族,该协议族包括了proto_ops中各个函数的具体实现,该函数在初始化网络栈的时候调用 |
int sock_unregister(int family) | 从内核中取消一个协议族,对应协议族中的proto_ops也没有了 |
struct socket *sock_alloc(void) | 分配一个socket结构,socket结构本质上位于inode结构体中,所以本质上是分配一个索引节点;inode和socket中有指针互相引用着 |
void sock_release(socket *sock) | 释放一个socket结构体,本质上是释放一个inode |
3,protocols.c
该文件定义链路层所使用协议的初始化函数,核心数据结构有如下两个:
struct net_proto{
char *name ; --------协议名称
void (*init_func)(struct net_proto*); ---------协议启动函数
}
struct net_proto protocols[] = { {"UNIX",unix_proto_init} , {"INET" , init_proto_init} , {"DDP",atalk_proto_init} }; -------协议数组,协议启动函数是什么时候注册进去的
4,socket.c
static struct file_operations socket_file_ops = {
sock_lseek, --------该函数在网络中没有实现
sock_read, --------socket读,从socket中读取数据到用户空间中
sock_write, --------socket写,将用户空间数据写入到socket中
NULL, /* readdir */ --------网络操作没有定义读目录操作
sock_select, ---------选择操作 ????
sock_ioctl, ---------对网络输入输出进行控制
NULL, /* mmap */ --------网络没有内存映射?
NULL, /* no special open code... */ --------网络套接字不用打开,socket系统调用已经起到了打开网络的作用
sock_close, --------网络关闭操作
NULL, /* no fsync */
sock_fasync
};
如上结构体是允许网络进行的文件操作,也即将网络也当作文件来看待
static struct proto_ops *pops[16]; --------该数组定义了网络协议族操作函数集合。比如对于INET域为inet_proto_ops,对于UNIX域为unix_proto_ops;
static int socket_in_use = 0 ; --------该全局变量定义了正在使用中的套接字个数
int sys_socketcall(int call , long *args); ------该函数为所用网络系统调用的总入口函数,call字段用来路由不同的内核函数, args为输入参数指针,整个函数描述罗列如下:
网络操作系统调用一览 sys_socketcall(call , args) | ||
call | 被路由到的函数 | 函数备注 |
SYS_SOCKET | sock_socket(int family,int type,int protocol) | 创建一个socket 1,根据family选择一个域操作函数集合(ops),inet_proto_ops/unix_proto_ops 2,type为socket的类型,SOCK_STREAM/SOCK_DGRAM 3,分配一个inode 4,分配一个socket , 将inode/socket用指针互相引用起来 5,初始化socket.type , socket.ops 6,调用INET层创建sock结构 7,为inode分配一个文件描述符,在file数据结构中会初始化该索引节点可以进行的文件操作,索引节点指针,读写指针位置 |
SYS_BIND | sock_bind(int fd , struct sockaddr *umyaddr , int addrlen) | 将一个本地IP地址绑定到本地套接字上,该套接字由文件描述符fd指定 1,调用INET层bind函数 |
SYS_CONNECT | sock_connect(int fd , struct sockaddr *uservaddr , int addrlen) | 建立本地套接字(fd)到远程服务器的连接,远程服务器地址由sockaddr指定 1,调用INET层connect函数 |
SYS_LISTEN | sock_listen(int fd , int backlog) | 该函数用于服务器端为接收客户端连接作准备,本质上也是调用INET层listen函数 该函数一般是服务器端调用 |
SYS_ACCEPT | sock_accept(int fd , struct sockaddr *upeer_sockaddr , int *upeer_addrlen) | 该函数用于服务器端在监听套接字(fd)上监听客户端的连接请求,如果有客户端的连接请求过来,该函数返回与客户端连接的新套接字的文件描述符,注意此时对于一个新过来的客户端连接,是新建立一个套接字,原套接字依然用于监听客户端连接请求 1,根据fd取得监听套接字sock 2,创建新套接字newsock(用来和客户端连接通信) 3,用sock信息初始化newsock(INET层dup) 4,调用INET层accept完成与远端的连接建立过程 5,为newsock分配文件描述符 6,如果upeer_sockaddr填写了,这里会填进去客户端的地址(调用INET层getname完成) |
SYS_GETSOCKNAME | sock_getsockname(int fd , struct sockaddr *usockaddr , int *usockaddr_len) | 该函数用于获取本地IP地址和端口号,本地IP地址和端口号是绑定于套接字(fd)sock结构中的,地址信息会在最后放到usockaddr中去 1,调用INET层getname函数 |
SYS_GETPEERNAME | sock_getpeername(int fd , struct sockaddr *usockaddr , int *) | 该函数用于获得远端IP地址和端口号,远端IP地址和端口号也是绑定与套接字(fd)sock结构中的,远端地址信息在最后会放到usockaddr中去 1,调用INET层getname函数 |
SYS_SEND | sock_send(int fd , void *buff , int len , long flags) | 该函数将用户空间buff开始的len个字节数据写入到socket中,socket由fd指定,这里的疑问是套接字中写入起始地址在哪里 1,调用INET层send函数 |
SYS_SENDTO | sock_sendto(int fd , void *buff , int len , long flages , struct sockaddr*addr , int addr_len) | 该函数将用户空间buff处开始的len个字节数据写入套接字中,不过该函数可以指定远端IP地址,但是对于TCP来说指定的远端IP地址必须是之前建立连接的IP地址(不明白,一个socket本来不就唯一对应一个IP地址吗) 1,调用INET层sendto函数 |
SYS_RECV | sock_recv(int fd , void *buff , int len , unsigned flags) | 该函数从套接字中读取len个字节的数据到用户空间buff开始的位置,套接字由fd指定 1,调用INET层recv函数 |
SYS_RECVFROM | sock_recvfrom(int fd,void *buff , int len , unsigned flags , struct sockaddr*addr,int *addr_len) | 该函数从套接字中读取len个字节的数据到用户空间buff开始的位置,同时返回远端的IP地址并放到addr中,该函数主要用于UDP 1,调用INET层recvfrom函数
|
SYS_SHUTDOWN | sock_shutdown(int fd , int how) | 该系统调用用来半关闭本地连接,用来告诉远端本地数据已经完全传输完毕,关闭本地到远程的连接通道。但是远程到本地的连接通道还没有关闭,远端依然可以给本地传送数据,该函数主要是设置相关标志位,不涉及网路数据的传输 1,调用INET层inet_shutdown函数 |
SYS_SETSOCKOPT | sock_setsockopt(int fd , int level , int optname , char *optval , int optlen) | 该函数用来设置套接字属性 |
SYS_GETSOCKOPT | sock_getsockopt(int fd , int level , int optname , char *optval , int *optlen) | 该函数用来提取套接字属性 |
上面列出来的是网络栈给用户暴漏的系统调用接口,下面介绍一些内核内部使用的函数:
socket.c中其他函数 | |
函数名 | 函数备注 |
int move_addr_to_kernel(void *uaddr , int ulen , void *kaddr) | 该函数的作用是将用户空间的数据拷贝到内核空间; 用户空间的起始地址为uaddr , 数据字节长度为ulen , 内核空间被写入的起始地址为kaddr |
int move_addr_to_user(void * kaddr , int klen , void * uaddr , int *ulen) | 该函数的作用是将内核空间的数据拷贝到用户空间; 内核空间的起始地址为kaddr , 被拷贝数据字节长度为min(klen , *ulen),用户空间被写入的起始地址为uaddr |
int get_fd(struct inode *inode) | 该函数的作用是为索引节点inode分配一个文件描述符 |
struct socket *socki_lookup(struct inode *inode) | 返回索引节点inode中的套接字socket节点的指针 |
struct socket *socki_lookup(int fd , struct **pfile) | 返回文件描述符fd指向的索引节点inode中socket节点的指针 |
struct socket *sock_alloc(void) | 分配一个新的套接字; 1,向内核申请一个inode 2,设置该inode->i_mode = S_IFSOCK; inode->i_sock = 1; 3,sock = &inode->u.socket_i;(socket本质上是inode中的一个结构体) 4,设置sock属性,sock上面的等待队列就是inode上的等待队列,设置指向inode的指针 5,sock_in_use ++ |
int sock_read(struct inode *inode , struct file *file , char *ubuf , int size) | 从套接字inode->u.socket_i中读取size个字节的数据到用户空间ubuf处,套接字中读取的起始地址是啥 1,调用INET层read |
int sock_write(struct inode *inode , struct file *file , char *ubuf,int size) | 从用户空间ubuf处开始写入size个字节的数据到inode->u.socket_i中去,套接字中被写入的起始地址是啥 1,调用INET层write |
int sock_select(struct inode *inode , struct file *file , int sel_type , select_table *wait) | 该函数干嘛的? 1,调用INET层select |
int sock_register(int family , struct proto_ops *ops) | 将网络协议族注册进pops数组中 1,选择一个空闲的ops[i] 2,ops[i] = ops 3,ops[i]->family = family
该函数何时被调用,proto_ops到底是怎么初始化的 |
int sock_unregister(int family) | 从网络协议族中撤销family网络协议 1,从ops数组中找到family网络协议ops[i] 2,ops[i]=null |
void proto_init(void) | 该函数用来初始化网络部分协议 实现方式为依次调用protocols数组中每个元素的init_func函数 |
void sock_init(void) | 该函数是系统网络栈初始化总入口函数,在start_kernel()函数中被调用 1,清空pops数组 2,proto_init() --------------网络协议族初始化 3,dev_init() --------------网卡驱动程序初始化 4,bh_base[NET_BH].routine = net_bh; -----网络中断下半部初始化,net_bh到底是啥 enable_bh(NET_BH) ----------使能网络中断下半部 |