inline_syscall##nr(name, args);// ##nr说明是该系统调用带有nr个args参数sendto系统调用的参数值是6,而name就是sendto 从上面的分析可以看出glibc将要执行的下面一条语句是 inline_syscall6(name,arg1,arg2,arg3,arg4,arg5,arg6) 在该函数中一段主要功能实现代码如下: __asm__ __volatile__ \ ("callsys # %0 %1 <= %2 %3 %4 %5 %6 %7 %8" \ : inline_syscall_r0_out_constraint (_sc_0), \ "=r"(_sc_19), "=r"(_sc_16), "=r"(_sc_17), \ "=r"(_sc_18), "=r"(_sc_20), "=r"(_sc_21) \ : "0"(_sc_0), "2"(_sc_16), "3"(_sc_17), "4"(_sc_18), \ "1"(_sc_19), "5"(_sc_20), "6"(_sc_21) \ : inline_syscall_clobbers); \ _sc_ret = _sc_0, _sc_err = _sc_19; 该代码采用了嵌入汇编(详细介绍查阅嵌入汇编相关书籍),其中: _sc_0=sendto; _sc_19 --_sc_21分别是arg1—arg6; inline_syscall_r0_out_constraint:功能相当于"=r",选用一个寄存器来存储输出变量。 "0"--"6"分别是%0--%6,代表_sc_0--_sc_21 接下来函数最终通过Linux中顶顶有名的INT 0X80陷入系统核心。具体的过程可以参考内核相关书籍。下面是一个兄弟对INT 0X80的简要介绍: http://blog./u2/65427/showart_712571.html 在陷入系统内核以后,最终会调用系统所提供的系统调用函数sys_sendto(),该函数直接调用了__sock_sendmsg(),该函数对进程做一个简单的权限检查之后就触发套接字(socket)中定义的虚拟sendmsg的函数,进而进入到下一层传输层处理。 Layer 4: 传输层(Transport Layer) 由上层的讨论可知,系统触发了sendmsg虚拟接口函数,其实就是传输层中的tcp_sendmsg或是udp_sendmsg,看你所使用的协议而定。本文介绍tcp_sendmsg(). 该函数需要做如下工作: 1) 为sk_buff(后面简称skb)分配空间,该函数首先尝试在套接字缓冲队列中寻找空闲空间,如果找不到就使用tcp_alloc_pskb()为其重新分配空间。 2) 下面这步就会tcp_sendmsg函数的主要部分了,将数据拷贝到缓冲区。它分为如下两种情况: 2.1)如果skb还有剩余空间的话,就使用skb_add_data()来向skb尾部添加数据包。代码如下: if (skb_tailroom(skb) > 0) { /* We have some space in skb head. Superb! */ if (copy > skb_tailroom(skb)) copy = skb_tailroom(skb); if ((err = skb_add_data(skb, from, copy)) != 0) goto do_fault; } 2.2)如果skb没有了可用空间,内核会使用TCP_PAGE宏来为发送的数据包分配一个高速缓存页空间,当该页被正确地分配后就调用Copy_from_user(to(page地址),from(usr空间),n)将用户空间数据包复制到page所在的地址空间。 但是我们都知道数据包在协议层之间的传输是通过skb的,难道将数据包复制到这个新分配的page中,内核就可以去睡大觉了吗?当然不是!接下来内核就要来处理这个问题了,那么怎样来处理呢? 此时就需要使用到skb中的另外一个数据区struct skb_shared_info[],但是该数据区在创建skb时是没有为其分配空间的,也就是说它开始纯粹就是个指针,而没有具体的告诉它要指向什么地方。这时大家应该知道它可以指向什么地方了,对,就是page!在内核中对这种情况的具体是通过fill_page_desc(struct sk_buff *skb,int I,struct page *page,int off,int size)来实现的,代码如下: static inline void fill_page_desc(struct sk_buff *skb, int i, struct page *page, int off, int size) { skb_frag_t *frag = &skb_shinfo(skb)->frags; frag->page = page; frag->page_offset = off; frag->size = size; skb_shinfo(skb)->nr_frags = i + 1; } 这里需要注意的是struct skb_shared_info[]只能通过skb_shinfo来获取,在该结构体中skb_flag_t类型的flags就是具体指向page的数组。 2.3)至此skb数据包的装载工作算是结束了,接下来就需要做一些后续工作,包括是否要分片,以及后来的TCP协议头的添加。先看在tcp_sendmsg()中的最后一个重要函数tcp_push,它的调用格式如下: static inline void tcp_push(struct sock *sk, struct tcp_opt *tp, int flags, int mss_now, int nonagle) 细心的朋友会发现,在该函数中传输的竟然不是skb,而是一个名为sock的结构体,那这又是什么东东呢?个人理解是它在顶层协议层之间(例如:应用层和传输层之间)的传输起着非常重要的作用,相当于沟通两层之间的纽带。再深入查找下该结构体的构成,我们很容易发现这样一个结构体变量:struct sk_buff_head,有名称我们可以知道它是用来描述skb头部信息的一个结构体,它指向了buffer的数据区。这下我们也明白了点,这个结构体其实还充当了一个队列作用,是用来存储skb的数据区。协议层之间传输完之后,具体到该层处理时内核就会从sk_buff_head逐个中取出skb数据区来处理,例如添加协议头等。 好了,tcp_sendmsg到此结束了它的使命了,下面将要需要的一个函数就是在tcp_push()中直接用到的一个函数:__tcp_push_pending_frames(),该函数又直接调用tcp_write_xmit()函数来进一步对数据包处理,它包括一下两步: 1) 检查是否需要对数据包进行分片,条件是只要skb中全部数据长度大于当前路由负荷量就需要分片。 2) 采用skb_clone(skb,GFP_ATOMIC)为TCP_HEAD分配一个sk_buff空间,这里需要注意的是skb_clone分配空间的特点,它首先是依照参数skb来来复制出一个新的sk_buff,新的skb和旧的skb共享数据变量缓存区,但是结构体缓冲区不是共享的,这似乎和copy on write机制有些相似。 3) 在分配了一个新的skb之后,内核就会执行tcp_transmit_skb().其实内核中是将2,3步合在一起的,如下: tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC)) 接下来就是tcp_transmit_skb函数的实现过程了。 1) 通过skb_push()在skb前面加入tcp协议头信息。这包括序列号,源地址,目的地址,校验和等。 2) 通过tcp_opt结构体(它是在该函数的开始部分从sock结构体中获得的)来访问tcp_func结构体中的.queue_xmit虚拟功能函数,在IPV4中是调用了ip_queue_xmit(),这样就进入了下一层——网络层。 未贴完,完整版在附件中。 关于驱动程序部分的声明: 由于本人没有实际开发过驱动程序,都是看了别人的驱动程序之后的体会和总结,所以难免会有错误,请各位不啬赐教! 接收过程待续。。。。 |
|
来自: jijo > 《linux内核分析》