http://blog.csdn.net/embeddedfly/article/details/6411765
我们接着上一节数据的接收来自看UDP的数据是如何发送的,上一节中我们贴出有关发送的代码,在那里只是为了让大家有一个印象
case SYS_SEND: err = sys_send( a0, ( void __user * ) a1, a[ 2] , a[ 3] ) ;
最终我们在上一节中看到发送数据都是调用的
__sock_sendmsg来完成的,朋友们忘记了可以回去看一下,我们直接贴出这个代码
static inline int __sock_sendmsg( struct kiocb * iocb, struct socket * sock, struct msghdr * msg, size_t size) { struct sock_iocb * si = kiocb_to_siocb( iocb) ; int err; si- > sock = sock; si- > scm = NULL ; si- > msg = msg; si- > size = size; err = security_socket_sendmsg( sock, msg, size) ; if ( err) return err; return sock- > ops- > sendmsg ( iocb, sock, msg, size) ; }
代码前面对比一下接收的__sock_recvmsg,可以发现基本相同,只是不同的地方是这里调用了UDP的sendmsg来发送数据,我们再次把钩子结构贴出来
static const struct proto_ops unix_dgram_ops = { 。。。。。。 . sendmsg = unix_dgram_sendmsg, 。。。。。。 } ;
可以看到调用了uinx_dgram_sendmsg
static int unix_dgram_sendmsg( struct kiocb * kiocb, struct socket * sock, struct msghdr * msg, size_t len) { struct sock_iocb * siocb = kiocb_to_siocb( kiocb) ; struct sock * sk = sock- > sk; struct net * net = sock_net( sk) ; struct unix_sock * u = unix_sk( sk) ; struct sockaddr_un * sunaddr= msg- > msg_name; struct sock * other = NULL ; int namelen = 0; /* fake GCC */ int err; unsigned hash; struct sk_buff * skb; long timeo; struct scm_cookie tmp_scm; if ( NULL = = siocb- > scm) siocb- > scm = & tmp_scm; err = scm_send( sock, msg, siocb- > scm) ; if ( err < 0) return err; err = - EOPNOTSUPP; if ( msg- > msg_flags& MSG_OOB ) goto out; if ( msg- > msg_namelen) { err = unix_mkname( sunaddr, msg- > msg_namelen, & hash) ; if ( err < 0) goto out; namelen = err; } else { sunaddr = NULL ; err = - ENOTCONN; other = unix_peer_get( sk) ; if ( ! other) goto out; } if ( test_bit( SOCK_PASSCRED, & sock- > flags) & & ! u- > addr & & ( err = unix_autobind( sock) ) ! = 0) goto out; err = - EMSGSIZE; if ( len > sk- > sk_sndbuf - 32) goto out;
上面代码中除了象接收socket一样做了必要的检查外调用了scm_send函数
static __inline__ int scm_send( struct socket * sock, struct msghdr * msg, struct scm_cookie * scm) { struct task_struct * p = current; scm- > creds. uid = p- > uid; scm- > creds. gid = p- > gid; scm- > creds. pid = task_tgid_vnr( p) ; scm- > fp = NULL ; scm- > seq = 0; unix_get_peersec_dgram( sock, scm) ; if ( msg- > msg_controllen < = 0) return 0; return __scm_send( sock, msg, scm) ; }
这里将进程的信息设置进scm_cookie中的creds身份结构中。
int __scm_send( struct socket * sock, struct msghdr * msg, struct scm_cookie * p) { struct cmsghdr * cmsg; int err; for ( cmsg = CMSG_FIRSTHDR ( msg) ; cmsg; cmsg = CMSG_NXTHDR ( msg, cmsg) ) { err = - EINVAL; /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */ /* The first check was omitted in <= 2.2.5. The reasoning was that parser checks cmsg_len in any case, so that additional check would be work duplication. But if cmsg_level is not SOL_SOCKET, we do not check for too short ancillary data object at all! Oops. OK, let's add it... */ if ( ! CMSG_OK( msg, cmsg) ) goto error ; if ( cmsg- > cmsg_level ! = SOL_SOCKET) continue ; switch ( cmsg- > cmsg_type) { case SCM_RIGHTS : err= scm_fp_copy( cmsg, & p- > fp) ; if ( err< 0) goto error ; break ; case SCM_CREDENTIALS : if ( cmsg- > cmsg_len ! = CMSG_LEN ( sizeof ( struct ucred) ) ) goto error ; memcpy ( & p- > creds, CMSG_DATA ( cmsg) , sizeof ( struct ucred) ) ; err = scm_check_creds( & p- > creds) ; if ( err) goto error ; break ; default : goto error ; } } if ( p- > fp & & ! p- > fp- > count ) { kfree( p- > fp) ; p- > fp = NULL ; } return 0; error : scm_destroy( p) ; return err; }
我们看到上面的代码与前面一节的
scm_recv是对应的,回到上面的主函数unix_dgram_sendmsg中,我们看到要判断msg->msg_namelen来确定是否提供了目标方的地址,如果有的话就要使用unix_mkname“格式化”地址。如果没有提供地址就说明已经通过connect已经设置了,所以要通过unix_peer_get得到目标地址的sock,并赋值给other指针。接着判断是否要求传送身份信息,但是要求了又没有为指定地址的话就要通过unix_autobind自动生成一个地址。len > sk->sk_sndbuf - 32是因为sk_sndbuf是保存缓存区大小的结构变量,其中要留出32个字节的控制信息。继续往下看
skb = sock_alloc_send_skb( sk, len, msg- > msg_flags& MSG_DONTWAIT , & err) ; if ( skb= = NULL ) goto out; memcpy ( UNIXCREDS( skb) , & siocb- > scm- > creds, sizeof ( struct ucred) ) ; if ( siocb- > scm- > fp) unix_attach_fds( siocb- > scm, skb) ; unix_get_secdata( siocb- > scm, skb) ; skb_reset_transport_header( skb) ; err = memcpy_fromiovec( skb_put( skb, len) , msg- > msg_iov, len) ; if ( err) goto out_free; timeo = sock_sndtimeo( sk, msg- > msg_flags & MSG_DONTWAIT ) ;
首先为发送缓冲区准备一个sk_buff,接着将我们上面准备的身份信息拷贝到这个结构中,然后进入unix_attach_fds中
static void unix_attach_fds( struct scm_cookie * scm, struct sk_buff * skb) { int i; for ( i= scm- > fp- > count - 1; i> = 0; i- - ) unix_inflight( scm- > fp- > fp[ i] ) ; UNIXCB( skb) . fp = scm- > fp; skb- > destructor = unix_destruct_fds; scm- > fp = NULL ; }
代码中调用了
void unix_inflight( struct file * fp) { struct sock * s = unix_get_socket( fp) ; if ( s) { struct unix_sock * u = unix_sk( s) ; spin_lock( & unix_gc_lock) ; if ( atomic_inc_return( & u- > inflight) = = 1) { BUG_ON( ! list_empty( & u- > link) ) ; list_add_tail( & u- > link, & gc_inflight_list) ; } else { BUG_ON( list_empty( & u- > link) ) ; } unix_tot_inflight+ + ; spin_unlock( & unix_gc_lock) ; } }
也就是说如果file是代表着sock的话,就要“记帐”了,以前我们看到在接收信息后要“冲帐”。然后函数调用了memcpy_fromiovec
int memcpy_fromiovec( unsigned char * kdata, struct iovec * iov, int len) { while ( len > 0) { if ( iov- > iov_len) { int copy = min_t( unsigned int , len, iov- > iov_len) ; if ( copy_from_user( kdata, iov- > iov_base, copy ) ) return - EFAULT; len - = copy ; kdata + = copy ; iov- > iov_base + = copy ; iov- > iov_len - = copy ; } iov+ + ; } return 0; }
这是其实就是将我们的数据拷贝到缓冲区中。数据在iovec结构变量指针处,我们看到是以此为地址拷贝到内核空间的,copy是确定要拷贝的数据大小。从msghdr结构中可以看出用户空间的缓冲区可以是分散的,而iovec负责指向这些数据缓冲区,而我们的sk_buff缓冲区只有一个所以这里的大小是所有用户缓冲区的长度和。而这个长度的变化是由skb_put来完成的
unsigned char * skb_put( struct sk_buff * skb, unsigned int len) { unsigned char * tmp = skb_tail_pointer( skb) ; SKB_LINEAR_ASSERT( skb) ; skb- > tail + = len; skb- > len + = len; if ( unlikely( skb- > tail > skb- > end) ) skb_over_panic( skb, len, __builtin_return_address( 0) ) ; return tmp; }
函数的意思是根据数据的长度调整tail指针和数据长度len结构变量。回到发送函数中,下面是调用了sock_sndtimeo
static inline long sock_sndtimeo( const struct sock * sk, int noblock) { return noblock ? 0 : sk- > sk_sndtimeo; }
代码很简单,要是不允许阻塞就要返回0了,否则就要返回设定的时间。回到函数往下看,接着要发送了
restart: if ( ! other) { err = - ECONNRESET; if ( sunaddr = = NULL ) goto out_free; other = unix_find_other( net, sunaddr, namelen, sk- > sk_type, hash, & err) ; if ( other= = NULL ) goto out_free; } unix_state_lock( other) ; err = - EPERM; if ( ! unix_may_send( sk, other) ) goto out_unlock; if ( sock_flag( other, SOCK_DEAD) ) { /* * Check with 1003.1g - what should * datagram error */ unix_state_unlock( other) ; sock_put( other) ; err = 0; unix_state_lock( sk) ; if ( unix_peer( sk) = = other) { unix_peer( sk) = NULL ; unix_state_unlock( sk) ; unix_dgram_disconnected( sk, other) ; sock_put( other) ; err = - ECONNREFUSED; } else { unix_state_unlock( sk) ; } other = NULL ; if ( err) goto out_free; goto restart; }
代码到这里朋友们如果前面一直跟着学习的话,并不难理解这里,如果我们上面已经找到了通过connect建立的sock,那么这时候other就已经指向了目标的sock,但是如果没有指向的话,就说明我们还没有走connect,而通过指定地址设置的目标的sock,所以这里unix_find_other找到目标的sock,此函数我们以前看过了,找到了目标的sock结构后,就要unix_may_send判断一下是否可以发送,以前我们也说过这个函数了。紧接着一个比较长的if判断语句段,首先我们看到是if (sock_flag(other, SOCK_DEAD)) 判断的目标方的sock是否还能够使用,接着我们继续往下看
err = - EPIPE; if ( other- > sk_shutdown & RCV_SHUTDOWN) goto out_unlock; if ( sk- > sk_type ! = SOCK_SEQPACKET ) { err = security_unix_may_send( sk- > sk_socket, other- > sk_socket) ; if ( err) goto out_unlock; } if ( unix_peer( other) ! = sk & & unix_recvq_full( other) ) { if ( ! timeo) { err = - EAGAIN; goto out_unlock; } timeo = unix_wait_for_peer( other, timeo) ; err = sock_intr_errno( timeo) ; if ( signal_pending( current) ) goto out_free; goto restart; } skb_queue_tail( & other- > sk_receive_queue, skb) ; unix_state_unlock( other) ; other- > sk_data_ready( other, len) ; sock_put( other) ; scm_destroy( siocb- > scm) ; return len; out_unlock: unix_state_unlock( other) ; out_free: kfree_skb( skb) ; out: if ( other) sock_put( other) ; scm_destroy( siocb- > scm) ; return err; }
结合以前章节中介绍的这里重点将缓冲区挂入到目标方的sock的sk_receive_queue队列头中,这是通过
void skb_queue_tail( struct sk_buff_head * list , struct sk_buff * newsk) { unsigned long flags; spin_lock_irqsave( & list - > lock, flags) ; __skb_queue_tail( list , newsk) ; spin_unlock_irqrestore( & list - > lock, flags) ; }
其余代码很简单了,到这里我们对UDP的socket数据的发送分析完了,因为UDP的数据发送和接收较TCP的简单,所以我们先介绍了,下一节我们对TCP的数据发送和接收分析一下。