您正在查看 "socket及其编程" 分类下的文章
2007年03月07日 星期三 上午 07:50
首先以socket和send两个系统调用为例,来回顾一下协议栈是如何工作的,在这过程中可以找到如何在协议栈中增加对UDP协议的支持。socket系统调用的原型是
int socket(int domain, int type, int protocol);
domain是协议域,对于ipv4协议来说,其值是PF_INET(ipv4因特网协议),对于我们自己实现的ipv4协议模块,我们为其新增MY_PF_INET。所有的协议域在include/linux/socket.h被定义,如下:
# |
2006年11月25日 星期六 上午 08:16
前面讲到,在数组inetsw_array中有INET域支持的全部套接字类型和协议类型,在一个结构体struct inet_protosw中,记录了一个套接字类型,该类型下的一种协议类型,以及该类型套接字的操作集和该类型协议的操作集,对于原始套接字,其协议类型为通配类型,通配所有网络层附属协议,其协议操作集为所有网络层附属协议的通过协议操作集,即struct proto raw_prot。在INET域进行初始化的时候,其要做的第一件事情就是通过函数proto_register注册协议操作集。
struct proto的成员slab是一个后备高速缓冲区,所有该类型下的网络层套接字struct sock都是从该后备高速缓冲区分配内存,struct proto的成员obj_size记录的是单个网络层套接字的空间大小,供分配slab时使用,其值是sizeof(struct raw_sock),raw_sock是对inet_sock的一个扩充(inet_sock是对sock的扩充)。在inet_create创建套接字的时候,调用sk_alloc创建struct sock,该函数直接从slab中分配内存。proto_register要做的第一件事情就是创建slab,struct proto还有成员rsk_prot和twsk_prot需要分配内存,也要在proto_register函数中完成,它是TCP协议相关的,暂时不关注。 |
2006年11月24日 星期五 下午 07:04
变量inetsw_array是inet域的一个全局数组,其类型是struct inet_protosw,该结构体的定义如下:
struct inet_protosw {
struct list_head list;
unsigned short type;
int protocol;
struct proto *prot;
const struct proto_ops *ops;
int capability;
char no_check;
|
2006年08月11日 星期五 下午 02:37
一般,用户在shell中使用ifconfig命令对网络接口进行参数配置,及接口的打开,关闭等操作。ifconfig实现网络接口配置的原理在于代表网络接口的结构体struct net_device的成员ip_ptr。前文已经讲过,ip_ptr实际指向的是一个结构体struct in_device,in_device有一个成员struct in_ifaddr *ifa_list,它指向一个链表,链表的每一项代表一个IP地址。对这个链表操作即可实现对网络接口的配置。 网络接口的操作命令按功能可以分为两组,第一组为查询命令:SIOCGIFADDR,SIOCGIFBRDADDR,SIOCGIFDSTADDR, SIOCGIFNETMASK。分别用于查询网络接口的IP地址,广播地址,目的地址,子网掩码。第二组为设置命令:SIOCSIFADDR, SIOCSIFFLAGS,SIOCSIFBRDADDR,SIOCSIFNETMASK,SIOCSIFDSTADDR。分别用于设置网络接口的IP地址,标志位,广播地址,子网掩码,目的地址。这些命令所要查询和设置的信息全部在结构体struct in_ifaddr中。 用户空间的应用程序通过系统调用ioctl使用这些命令,ioctl的函数原型如下: #include <sys/ioctl.h> |
2006年08月11日 星期五 下午 02:26
套接字写有多个实现接口,我们只以其中一个接口write为线索,对套接字写(网络数据发送)的流程进行分析。系统调用write会调用内核函数sys_write,sys_write调用vfs_write完成实际的写操作。 vfs_write会先调用file->f_op->write(file从套接字描述符获得)。如果file->f_op-> write不存在,则调用do_sync_write。该函数会调用sock_aio_write,sock_aio_write又会调用 __sock_sendmsg,然后到myinet_sendmsg,最后才到sk->sk_prot->sendmsg,对于RAW协议来讲,即myraw_sendmsg。 sock_aio_write的函数原型如下: static ssize_t sock_aio_write(struct kiocb *iocb, const char __user *ubuf, size_t size, loff_t pos) ubuf是用户待发送数据,size是数据长度,pos是文件位置(永远为零)。在这个函数里,会把用户待发送数据封装成一个struct msghdr结构: |
2006年08月11日 星期五 下午 02:26
继续讲关于myraw_setsockopt的实现,如果level是SOL_IP,则调用myip_setsockopt函数。 myip_setsockopt的操作对像是struct socket sock的成员struct sock sk。并把sk强制转化为struct inet_sock: inet = inet_sk(sk)。 如果option_name在MRT_BASE和MRT_BASE+10之间,则调用myip_mroute_setsockopt函数,关于mroute,后面再给出分析。 IP_OPTIONS:设置将由该套接字发送的每个包的IP选项。 其option_value是一个结构体struct ip_options。该选项首先分配一个这样的结构体,然后用这个结构体替代inet->opt指向的结构体。如果协议类型是 SOCK_STREAM的话,从struct tcp_sock *tp中,tp->ext_header_len减去旧的inet->opt->optlen, 再加上新的opt->optlen。最后调用tcp_sync_mss进行同步,有关TCP的一些细节,我们在实现TCP协议时再分析。 IP_PKTINFO:传递一条包含pktinfo结构(该结构提供一些来访包的相关信息)的IP_PKTINFO辅助信息。 这个选项只对数据报类的套接字有效。 struct in_pkt |
2006年08月11日 星期五 下午 02:25
如果不在套接字级别上设置选项,即setsockopt系统调用的参数level不设为SOL_SOCKET,那么sys_setsockopt的实现会直接调用sock->ops->setsockopt。对MY_PF_INET域的RAW协议来讲,sock->ops = myinet_sockraw_ops,而myinet_sockraw_ops.setsockopt = sock_common_setsockopt。 而sock_common_setsockopt直接调用sock->sk->sk_prot->setsockopt。对于RAW协议来讲,即myraw_setsockopt。 下面关注myraw_setsockopt的实现。对于RAW协议来讲,level还可以有两种取值:SOL_IP和SOL_RAW。 myraw_setsockopt首先检查level是否为SOL_IP,如果是,调用myip_setsockopt函数,该函数实现IP级别上的选项,否则,为SOL_RAW级别上的选项,SOL_RAW级别上只有一个选项,即ICMP_FILTER,在MY_IPPROTO_ICMP协议下有效。它激活绑定到MY_IPPROTO_ICMP协议的一个用于myraw socket特殊的过滤器。该值对每种ICMP消息都有一个位(掩码),可以把那种ICMP消息过滤掉,缺省时是不过滤ICMP消息。 对于ICMP_FILTER选项,myraw_setsockopt调用myraw_seticmpfilter函数,它把option_value赋 |
2006年08月11日 星期五 下午 02:24
(接上文) SO_KEEPALIVE,套接字保活。 如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时器,否则关闭保活定时器。对于所有协议,该操作都会根据option_value置或清sock->sk->sk_flag中的 SOCK_KEEPOPEN位。 SO_OOBINLINE,紧急数据放入普通数据流。 该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。 SO_NO_CHECK,打开或关闭校验和。 该操作根据option_value的值,设置sock->sk->sk_no_check。 SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。 这个值在0到6之间(包括0和6),由option_value指定。赋给sock->sk->sk_priority。 SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用 |
2006年08月11日 星期五 下午 02:24
套接字选项这个话题在socket编程里,可能已经属于中高级话题了,之所以在一开始就把这个话题提上来讲,是因为我们的一个近阶段目标是能够把 MY_PF_INET域的RAW协议走通,并在上面跑起一个ping程序,所以,按照ping程序的要求,接下来,我们必须实现套接字选项系统调用 setsockopt在MY_PF_INET中RAW协议中的相关实现。 下面是该系统调用函数的原型: #include <sys/socket.h> int setsockopt( int socket, int level, int option_name, const void *option_value, size_t option_len); 第一个参数socket是套接字描述符。第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level,以linux 2.6内核为例(在不同的平台上,这种关系可能会有不同),在套接字级别上(SO |
2006年08月11日 星期五 下午 02:23
我们先来简单看一下系统调用close(int fd)的流程。该系统调用会调用到内核中的函数: asmlinkage long sys_close(unsigned int fd) 参数fd给我们一个很好的线索,我们可以很方便地找到相应的struct file结构:file = current->files->fd[fd]。取出了这个至关重要的数据结构后,我们归还fd给系统,同时,设current-> file->fd[fd]=NULL。使我们创建的socket完全跟系统和进程分离。最后再销毁struct file结构。 销毁struct file的很多细节我们不关注,但在某一步,一个叫__fput(struct file *file)的函数中,有这样一个调用: file->f_op->release(inode, file); 它实际调用到了sock_close函数,该函数又会调用到sock_release函数。sock_release函数又调用我们my_inet模块提供的myinet_release函数完成实际的socket销毁工作,同时,释放inode。 上面讲述的是一个大致的流程,我们重点关注的还是如何在我们的my_inet模块中实现套接字的销毁。下面先看一下myinet_release函 |
2006年08月11日 星期五 下午 02:22
要使我们的工作得以顺利进行,我们必须把建立与销毁,注册与注销等配对的工作放在一起完成,才能使模块始终处于一个可使用的状态。所以,完成了套接字的创建,我们下一步紧接着面临的工作就是套接字的销毁。 在系统调用层,套接字也是一个文件描述符来表示,所以,关闭套接字跟关闭打开着的文件并没有区别,都是使用close(fd),但同样一个操作,在内核中却发生着很不一样的操作。 我们还需要从socket系统调用创建套接字讲起,当代表套接字的一个struct socket结构被完整创建出来以后,它被映射到一个文件描述符,并且系统把这个文件描述符返回给用户。现在,我们就需要简单了解这个映射过程是怎么样的。 因为我们的目标是重新建立一个INET域的代码,所以不想在关于文件系统的方面走太远,所以只进行简单介绍。我们首先要从系统获取一个未使用的文件描述符,并创建一个struct file结构。同时,我们初始化这个struct file结构,整个初始化过程我们只需要关注其中两步: sock->file = file; |
2006年08月11日 星期五 下午 02:22
我们已经完成了MY_PF_INET域的初始化,虽然留了很多空,但我们至少已经具备了:TCP, UDP, RAW三种协议;TCP, UDP, ICMP, IGMP四种基本协议;inetsw数组。有了这些,我们可以尝试着创建一个套接字试试。 关于套接字创建的执行流程,前文已有描述,其最终会进入我们的family中的创建函数: static int myinet_create(struct socket *sock, int protocol); 套接字类型已经包含在sock结构中。MY_PF_INET域中有效的类型是SOCK_STREAM, SOCK_DGRAM和SOCK_RAW。据此,我们定位到inetsw数组的某一项(一个链表的链表头),然后在这个链表中匹配protocol。 MY_PF_INET域中的常用的protocol是:IPPROTO_IP, IPPROTO_ICMP, IPPROTO_IGMP, IPPROTO_TCP, IPPROTO_UDP。其中IPPROTO_IP比较特殊,是一个通配符。链表中的protocol匹配可以是严格匹配,也可以是通配符匹配,但最终 protocol必须有一个确定的值,而不能是IPPROTO_IP。因为MY_PF_INET域中inetsw数组只有三项(SOCK_STREAM, IPPROTO_TCP), (SOCK_DGRAM, IPPROTO_UDP), (SOCK_ |
2006年08月11日 星期五 下午 02:18
前面讲到在sys_socket函数中,有一步是调用net_families[family]->create完成最后的创建工作,下面就以inet域的创建来解释这最后一步的创建工作: 1、设socket->state = SS_UNCONNECTED。 2、从数组inetsw中匹配套接字类型和协议类型。inetsw是一个链表数组,也就是说数组的每一项是一个链表,同套接字类型的在同一个链表中。比如,用户这样创建一个TCP协议的套接字: socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) 最终,内核在inetsw中匹配到的是这样一个结构体: static struct inet_protosw inetsw_array[] = { { .type = SOCK_STREAM, .protocol = IPPROTO_TCP, |
2006年08月11日 星期五 下午 02:17
结构体sock是套接口在网络层的表示,在代码include/net/sock.h 174行定义,下面是其内容: struct sock { struct sock_common __sk_common; #define sk_family __sk_common.skc_family #define sk_state __sk_common.skc_state #define sk_reuse __sk_common.skc_reuse #define sk_bound_dev_if __sk_common.skc_bound_dev_if #define sk_node __sk_common.skc_node #define sk_bind_node __sk_common.skc_bind_node #define sk_refcnt __sk_common.skc_refcnt unsigned char sk_shutdown : 2, |
2006年08月11日 星期五 下午 02:16
一个socket代表了通信链路的一端,存储或指向与链路有关的所有信息。Linux提供了创建socket的一个系统调用,通过该系统调用,能够得到一个用来访问套接字的描述符: #include <sys/types.h> #include <sys/socket.h> int socket( int domain, int type, int protocol ); 内核中的系统调用函数原型是在net/socket.c 1180行: asmlinkage long sys_socket( int family, int type, int protocol ); 该函数主要做了两件事情:创建一个代表通讯端点的结构体struct socket,将这个结构映射到一个文件描述符上,最后将这个描述符返回,也就是我们调用socket得到的套接字描述符。 下面是Linux内核中对结构socket的定义(不同操作系统间,对该结构的定义会有差异): struct socket { |
|
| |