Socket層的核心是兩個(gè)函數(shù):sosend()
和soreceive()
.這兩個(gè)函數(shù)負(fù)責(zé)處理所有Socket層和協(xié)議層之間的I/O操作。
select()
系統(tǒng)調(diào)用的作用是監(jiān)控文件描述符的狀態(tài)。一般用于Socket I/O操作,也可以用于其它文件I/O操作。
Socket緩存
我們知道,每一個(gè)Socket都有一個(gè)發(fā)送緩存和一個(gè)接收緩存。緩存的類型為sockbuf
。
struct sockbuf{ struct mbuf * sb_mb;//mbuf鏈,用于存儲(chǔ)用戶數(shù)據(jù) u_long sb_mbcnt;//mbuf鏈的長度 u_long sb_mbmax;//mbuf鏈的最大長度。 u_long sb_cc;//緩存中的實(shí)際字節(jié)數(shù) u_long sb_hiwat; long sb_lowat; struct selinfo sb_sel; short sb_flags; short sb_timeo;//read/write超時(shí)時(shí)間 }
sb_mb
為指向存儲(chǔ)數(shù)據(jù)的mbuf鏈的指針,指向的是mbuf鏈的第一個(gè)mbufsb_cc
的值等于存儲(chǔ)在mbuf鏈中的數(shù)據(jù)字節(jié)數(shù),即mbuf鏈中存儲(chǔ)的有效數(shù)據(jù)的字節(jié)數(shù)sb_hiwat
和sb_lowat
用來調(diào)整Socket的流控算法(不是TCP的流量控制)sb_mbcnt
為分配給緩存中的所有mbuf的存儲(chǔ)器數(shù)量,即mbuf鏈的長度sb_mbmax
為分配給該Socket mbuf緩存的存儲(chǔ)器數(shù)量的上限,即mbuf鏈的最大長度。
默認(rèn)的上限在socket系統(tǒng)調(diào)用中發(fā)送PRU_ATTACH
請(qǐng)求時(shí)由協(xié)議設(shè)置。只要內(nèi)核要求的每個(gè)socket緩存的大小不超過262 144個(gè)字節(jié)的限制(sb_max,這是一個(gè)全局變量),進(jìn)程就可以修改緩存的上限和下限。
Internet協(xié)議的默認(rèn)的Socket緩存限制
因?yàn)槊恳粋€(gè)收到的UDP數(shù)據(jù)報(bào)的源地址和其攜帶的數(shù)據(jù)一起排隊(duì),所以UDP協(xié)議的sb_hiwat的默認(rèn)值設(shè)置為能容納40個(gè)1K字節(jié)長的數(shù)據(jù)報(bào)和相應(yīng)的sockaddr_in結(jié)構(gòu)(每個(gè)16個(gè)字節(jié))。
sb_sel是用來實(shí)現(xiàn)
select()
系統(tǒng)調(diào)用的selinfo結(jié)構(gòu)sb_timeo用來限制一個(gè)進(jìn)程在讀寫調(diào)用中被阻塞的時(shí)間。
下表列出了sb_flags的所有可能的值
sb-flags | 說明 |
---|---|
SB_LOCK | 一個(gè)進(jìn)程已經(jīng)鎖定了socket緩存 |
SB_WANT | 一個(gè)進(jìn)程正在等待給socket緩存加鎖 |
SB_WAIT | 一個(gè)進(jìn)程正在等待接收數(shù)據(jù)或發(fā)送數(shù)據(jù)所需的緩存 |
SB_SEL | 一個(gè)或多個(gè)進(jìn)程正在選擇這個(gè)緩存 |
SB_ASYNC | 為這個(gè)緩存產(chǎn)生異步I/O信號(hào) |
SB_NOINTR | 信號(hào)不取消加鎖請(qǐng)求 |
SB_NOTIFY | (SB_WAIT|SB_SEL|SB_ASYNC ) 一個(gè)進(jìn)程正在等待緩存的變化,如果緩存發(fā)送任何變化,用wakeup通知該進(jìn)程 |
write、writev、sendto和sendmsg系統(tǒng)調(diào)用
我們將write()
、writev
、sendto()
和sendmsg()
四個(gè)系統(tǒng)調(diào)用統(tǒng)稱為"寫系統(tǒng)調(diào)用",它們的作用是往網(wǎng)絡(luò)連接上發(fā)送數(shù)據(jù)。相對(duì)于最一般的調(diào)用sendmsg()
而言,前三個(gè)系統(tǒng)調(diào)用是比較簡單的接口。
所有的寫系統(tǒng)調(diào)用都要直接或間接地調(diào)用sosend
。sosend
的功能是將進(jìn)程來的數(shù)據(jù)復(fù)制到內(nèi)核,并將數(shù)據(jù)傳遞給與socket相關(guān)的協(xié)議。
函數(shù) | 描述符類型 | 緩存數(shù)量 | 是否指明目的地址 | 標(biāo)志? | 控制信息? |
---|---|---|---|---|---|
write | 任何類型 | 1 | |||
writev | 任何類型 | [1..UIO_MAXIOV] | |||
send | socket | 1 | . | ||
sendto | socket | 1 | . | . | |
sendmsg | socket | [1..UIO_MAXIOV] | . | . | . |
write()
和writev()
系統(tǒng)調(diào)用適用于任何描述符,而其它的系統(tǒng)調(diào)用只適用于socket描述符。
writev()
和sendmsg()
系統(tǒng)調(diào)用可以接收從多個(gè)(應(yīng)用層)緩存中來的數(shù)據(jù)。從多個(gè)緩存中寫數(shù)據(jù)稱為"收集(gathering)",同它相對(duì)應(yīng)的讀操作稱為"分散(scattering)"。執(zhí)行收集操作時(shí),內(nèi)核按序接收類型為iovec
的數(shù)據(jù)中指定的緩存中的數(shù)據(jù)。數(shù)組最多有UIO_MAXIOV
個(gè)單元。
struct iovec { char * iov_base;//基線地址,指向長度為iov_len個(gè)字節(jié)的緩存的開始 size_t iov_len;//長度};
如果沒有這種接口(writev),一個(gè)進(jìn)程將不得不將多個(gè)緩存復(fù)制到一個(gè)大的緩存中,或調(diào)用多個(gè)寫系統(tǒng)調(diào)用來發(fā)送多個(gè)緩存中的數(shù)據(jù)。對(duì)于數(shù)據(jù)報(bào)協(xié)議而言,調(diào)用一次write()
就是發(fā)送一個(gè)數(shù)據(jù)報(bào),數(shù)據(jù)報(bào)的發(fā)送不能用多個(gè)寫動(dòng)作來實(shí)現(xiàn)。
數(shù)據(jù)報(bào)協(xié)議要求每一個(gè)寫調(diào)用必須指定一個(gè)目的地址。因?yàn)?code style="margin: 1px 5px; padding: 0px 5px !important; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: "Courier New", sans-serif !important; font-size: 12px !important; background-color: rgb(245, 245, 245) !important; border: 1px solid rgb(204, 204, 204) !important; border-radius: 3px !important;">write()、writev()
和send()
調(diào)用接口不支持對(duì)目的地址的指定,因此這些調(diào)用只能在調(diào)用connect()
將目的地址同一個(gè)無連接的socket聯(lián)系起來后才能被調(diào)用。調(diào)用sendto()
或sendmsg()
時(shí)必須提供目的地址,或在調(diào)用它們之前調(diào)用connect()
來指定目的地址。
sendmsg()系統(tǒng)調(diào)用
只有通過sendmsg()
系統(tǒng)調(diào)用才能訪問到與socket API的輸出有關(guān)的所有功能。sendmsg()
和sendit()
函數(shù)準(zhǔn)備sosend()
系統(tǒng)調(diào)用所需的數(shù)據(jù)結(jié)構(gòu),然后由sosend()
系統(tǒng)調(diào)用將報(bào)文發(fā)送給相應(yīng)的協(xié)議。
對(duì)于SOCK_DGRAM
協(xié)議而言,報(bào)文就是數(shù)據(jù)報(bào)。對(duì)于SOCK_STREAM
協(xié)議而言,報(bào)文是一串字節(jié)流。
sendmsg()
有三個(gè)參數(shù):socket描述符、指向msghdr結(jié)構(gòu)的指針、幾個(gè)控制標(biāo)志。函數(shù)copyin()
將msghdr
結(jié)構(gòu)從用戶空間復(fù)制到內(nèi)核空間。
struct msghdr { caddr_t msg_name;//可選的目的地址 u_int msg_namelen;//msg_name的長度 struct iovec * msg_iov;//分散/收集數(shù)組 u_int msg_iovlen;//msg_iov數(shù)組長度 caddr_t msg_control;//控制信息 u_int msg_controllen;//控制信息長度 int msg_flags;//接收標(biāo)志};
控制信息(msg_control字段)的類型為cmsghdr
結(jié)構(gòu):
struct cmsghdr { u_int cmsg_len; int cmsg_level; int cmsg_type; };
struct sendmsg_args { int s; caddr_t msg; int flags; }; sendmsg(struct proc * p,struct sendmsg_args * uap,int * retval) { struct msghdr msg; struct iovec aiov[UIO_SMALLOV],*iov; int error; /** *一個(gè)有8個(gè)元素(UIO_SMALLIOV)的iovec數(shù)組從棧中自動(dòng)分配。 *如果分配的數(shù)組不夠大,sendmsg將調(diào)用MALLOC分配更大的數(shù)組。如果進(jìn)程指定的數(shù)組單元大于1024(UIO_MAXIOV),則返回EMSGSIZE。 *copyin將iovec數(shù)組從用戶空間復(fù)制到棧中的數(shù)組或一個(gè)更大的動(dòng)態(tài)分配的數(shù)組中。 *這種技術(shù)避免了調(diào)用malloc帶來的高代價(jià),因?yàn)榇蠖鄶?shù)情況下,數(shù)組的單元數(shù)小于等于8 */ //將msg數(shù)據(jù)從用戶空間復(fù)制到內(nèi)核空間 if(error = copyin(uap->msg,(caddr_t)&msg,sizeof(msg))){ return (error); } if((u_int)msg.msg_iovlen >= UIO_SMALLIOV){ if((u_int)msg.msg_iovlen >= UIO_MAXIOV){ return (EMSGSIZE); } MALLOC(iov,struct iovec *,sizeof(struct iovec)*(u_int)msg.msg_iovlen,M_IOV,M_WAITOK); }else{ iov = aiov; } if(msg.msg_iovlen && (error = copyin((caddr_t)msg.msg_iov,(caddr_t)iov,(unsigned)(msg.msg_iovlen * sizeof(struct iovec))))){ goto done; } msg.msg_iov = iov; //如果sendit返回,則表明數(shù)據(jù)已經(jīng)發(fā)送給相應(yīng)的協(xié)議或出現(xiàn)差錯(cuò) error = sendit(p,uap->s,&msg,uap->flags,retval); done: if(iov != aiov){ FREE(iov,M_IOV); } return (error); }
sendit系統(tǒng)調(diào)用
sendit(struct proc * p,int s,struct msghdr * mp,int flags,int * retsize) { struct file * fp; struct uio auio; struct iovec * iov; int i; struct mbuf * to, * control; int len,error; if(error = getsock(p->p_fd,s,&fp)){ return error; } /** *初始化uio結(jié)構(gòu),并將應(yīng)用層的輸出緩存中的數(shù)據(jù)收集到內(nèi)核緩存中 */ auio.uio_iov = mp->msg_iov; auio.uio_iovcnt = mp->msg_iovlen; auio.uio_segflg = UIO_USERSPACE; auio.uio_rw = UIO_WRITE; auio.uio_procp = p; auio.uio_offset = 0; auio.uio_resid = 0; iov = mp->msg_iov; /** *將傳送的數(shù)據(jù)的長度通過一個(gè)for循環(huán)來計(jì)算 */ for(i = 0;i < mp->msg_iovlen;i++,iov++){ /** *保證緩存的長度非負(fù) */ if(iov->iov_len < 0){ return (EINVAL); } /** *保證uio_resid不溢出,因?yàn)閡io_resid是一個(gè)**有符號(hào)的**整數(shù),且iov_len要求非負(fù) */ if((auio.uio_resid += iov->iov_len) < 0){ return (EINVAL); } } /** *如果進(jìn)程提供了地址和控制信息,則sockargs將地址和控制信息復(fù)制到內(nèi)核緩存中 */ if(mp->msg_name){//如果進(jìn)程提供了地址 //將地址復(fù)制到內(nèi)核中 if(error = sockargs(&to,mp->msg_name,mp->msg_namelen,MT_SONAME)){ return (error); } }else{ top = 0; } if(mp->msg_control){//如果進(jìn)程提供了控制信息 if(mp->msg_controllen < sizeof(struct cmsghdr)){ error = EINVAL; goto bad;; } //將控制信息復(fù)制到內(nèi)核中 if(error = sockargs(&control,mp->msg_control,mp->msg_controllen,MT_CONTROL)){ goto bad; } }else{ control = 0; } /** *發(fā)送數(shù)據(jù)和清除緩存 */ len = auio.uio_resid;//為了防止sosend不接受所有數(shù)據(jù)而無法計(jì)算傳送的字節(jié)數(shù),將uio_resid的值保存在len中 //將socket、目的地址、uio結(jié)構(gòu)(包含了要發(fā)送的數(shù)據(jù))、控制信息和標(biāo)志全部傳給函數(shù)sosend if(error = sosend((struct socket *)fp->f_data,to,&auio,(struct mbuf*)0,control,fkags)){ if(auio.uio_resid != len && (error == ERESTART || error == EINTR || error == EWOULDBLOCK)){ error = 0; } if(error == EPIPE){ psignal(p,SIGPIPE); } } if(0 == error){ /** *如果沒有差錯(cuò)出現(xiàn)(或差錯(cuò)被丟棄),則計(jì)算傳送的字節(jié)數(shù),并將其保存在*retsize中。 *如果sendit返回0,syscall返回*retsize給進(jìn)程而不是返回差錯(cuò)代碼 */ *retsize = len - auio.uio_resid; } bad://釋放包含目的地址的緩存。 if(to){ m_freem(to); } return (error); }
sosend系統(tǒng)調(diào)用
sosend()
是socket層中最復(fù)雜的函數(shù)之一。前面提到的所有五個(gè)寫系統(tǒng)調(diào)用最終都要調(diào)用sosend()
。sosend()
的功能就是:根據(jù)socket指明的協(xié)議支持的語義和緩存的限制,將數(shù)據(jù)和控制信息傳遞給socket指明的協(xié)議的pr_usrreq函數(shù).sosend從不將數(shù)據(jù)放在發(fā)送緩存(輸出緩存)中,存儲(chǔ)和移走數(shù)據(jù)應(yīng)由協(xié)議來完成。
sosend()
對(duì)發(fā)送緩存的sb_hiwat
和sb_lowat
值的解釋,取決于對(duì)應(yīng)的協(xié)議是否實(shí)現(xiàn)可靠或不可靠的數(shù)據(jù)傳送功能。
可靠的協(xié)議緩存
對(duì)于提供可靠的數(shù)據(jù)傳輸協(xié)議,例如TCP,發(fā)送緩存保存了還沒有發(fā)送的數(shù)據(jù)和已經(jīng)發(fā)送但還沒有被確認(rèn)的數(shù)據(jù)。sb_cc
等于發(fā)送緩存的數(shù)據(jù)的字節(jié)數(shù),且0 <= sb_cc <= sb_hiwat
。
如果有帶外數(shù)據(jù)發(fā)送,則sb_cc有可能暫時(shí)超過sb_hiwat
so_send應(yīng)該確保在通過pr_usrreq()
函數(shù)將數(shù)據(jù)傳遞給協(xié)議層之前有足夠的發(fā)送緩存。
協(xié)議層會(huì)將數(shù)據(jù)放到發(fā)送緩存中。sosend通過下面兩種方式之一將數(shù)據(jù)傳送給協(xié)議層:
TCP應(yīng)用程序?qū)ν獬龅腡CP報(bào)文段的大小沒有控制。例如,在TCP Socket上發(fā)送一個(gè)長度為4096字節(jié)的報(bào)文,假定發(fā)送緩存中有足夠的緩存,則Socket層將該報(bào)文分成兩部分,每一部分長度為2048個(gè)字節(jié),分別存放在一個(gè)帶外部簇的mbuf中。然后,在協(xié)議處理時(shí),TCP將根據(jù)連接上的MSS將數(shù)據(jù)分段,通常情況下,MSS為2048個(gè)字節(jié)。
當(dāng)一個(gè)報(bào)文因?yàn)樘蠖鴽]有足夠的緩存時(shí),協(xié)議允許報(bào)文被分成多段。但sosend仍然不將數(shù)據(jù)傳送給協(xié)議層知道發(fā)送緩存中的空閑空間大小大于sb_lowat。對(duì)于TCP而言,sb_lowat的默認(rèn)值是2048,從而阻止Socket層在發(fā)送緩存快滿時(shí)用小塊數(shù)據(jù)干擾TCP.
不可靠的協(xié)議緩存
對(duì)于提供不可靠的數(shù)據(jù)傳輸?shù)膮f(xié)議而言,例如UDP,發(fā)送緩存不需要保存任何數(shù)據(jù),也不等待任何確認(rèn)。
每一個(gè)報(bào)文一旦被排隊(duì)等待發(fā)送到相應(yīng)的網(wǎng)絡(luò)設(shè)備,Socket層立即將它傳送到協(xié)議。在這種情況下,sb_cc總是等于0,sb_hiwat指定每一次寫的最大長度,間接指明數(shù)據(jù)報(bào)的最大長度。
UDP協(xié)議的sb_hiwat
的默認(rèn)值為9216(9 x 1024)。如果進(jìn)程沒有通過SO_SNDBUF
Socket選項(xiàng)改變sb_hiwat
的值,則發(fā)送長度大于9216個(gè)字節(jié)的數(shù)據(jù)報(bào)將導(dǎo)致差錯(cuò)。不僅如此,其它的協(xié)議限制也可能不允許一個(gè)進(jìn)程發(fā)送大的數(shù)據(jù)報(bào)。
對(duì)于NFS寫而言,9216已足夠大,NFS寫的數(shù)據(jù)加上協(xié)議首部的長度一般默認(rèn)為8192個(gè)字節(jié)。
實(shí)現(xiàn)
sosend(struct socket * so,struct mbuf * addr,struct uio * uio,struct mbuf * top,struct mbuf * control,int flags) { /** *初始化 */ struct proc * p = curproc; struct mbuf **mp; struct mbuf * m; long space,len,resid; int clen = 0,error,s,dontroute,mlen; /** * 如果sosendallatonce等于true(任何設(shè)置了PR_ATOMIC的協(xié)議)或數(shù)據(jù)已經(jīng)通過top中的mbuf鏈傳送給sosend,則將設(shè)置atomic。這個(gè)標(biāo)志[控制數(shù)據(jù)是作為一個(gè)mbuf鏈還是作為獨(dú)立的mbuf傳送給協(xié)議]. */ int automic = sosendallatonce(so) || top; /** *resid等于iovec緩存中的數(shù)據(jù)字節(jié)數(shù)或top中的mbuf鏈中的數(shù)據(jù)字節(jié)數(shù) */ if(uio){ resid = uio->uio_resid; }else{ resid = top->m_pkthdr.len; } if(resid < 0){ return (EINVAL); } /** *如果僅僅要求對(duì)這個(gè)報(bào)文不通過路由表進(jìn)行路由選擇,則設(shè)置dontroute */ dontroute = (flags & MSG_DONTROUTE) && (so->so_options & SO_DONTROUTE) == 0 && (so->so_proto->pr_flags & PR_ATOMIC); p->p_stats->p_ru.ru_msgsnd++; if(control){ /** * clen等于在可選的控制緩存中的字節(jié)數(shù) */ clen = control->m_len; } #define snderr(errno) {error = errno;splx(s);goto release;} /** *sosend的主循環(huán)從restart開始,在循環(huán)的開始調(diào)用sblock()給發(fā)送緩存加鎖。通過加鎖確保多個(gè)進(jìn)程按序互斥訪問socket緩存 */restart: if(error = sblock(&so->so_snd,SBLOCKWAIT(flags))) { goto out; } /** *主循環(huán)直到[將所有數(shù)據(jù)都傳送給協(xié)議層](即resid = 0)時(shí)才會(huì)退出 */ do{ /** * 等待發(fā)送緩存有空閑空間 */ s = splnet(); /** * 如果Socket輸出被禁止,即TCP連接的寫通道已經(jīng)關(guān)閉,則返回EPIPE */ if(so->so_state & SS_CANTSENDMORE){ snderr(EPIPE); } /** * 如果Socket正處于差錯(cuò)狀態(tài)(例如,前一個(gè)數(shù)據(jù)報(bào)可能已經(jīng)產(chǎn)生了一個(gè)ICMP不可達(dá)的差錯(cuò)),則返回so->so_error */ if(so->so_error){ snderr(so->so_error); } /** * 如果協(xié)議請(qǐng)求連接且連接還沒有建立或連接請(qǐng)求還沒有啟動(dòng),則返回EMOTCONN. * sosend允許只有控制信息但沒有數(shù)據(jù)的寫操作 */ if((so->so_state & SS_ISCONNECTED) == 0){ if(so->so_proto->pr_flags & PR_CONNREQUIRED){ if((so->so_state & SS_ISCONFIRMING) == 0 && !(resid == 0 && clen != 0)){ snderr(ENOTCONN); } }else if(addr == 0){ snderr(EDESTADDRREQ); }
http://www.cnblogs.com/kakawater/p/7112817.html