(萊昂氏unix原始碼分析導讀-15) 系統初啟(8)
程序user態下的分段
User態中將程序空間分為text、data、stack segment三部分。
estabur(nt, nd, ns,sep)根據各個segment的大小,為各段分配page,引數如下:
(1) nt—— text segment的長度(block)
(2) nd—— data segment的長度(block)
(3) ns—— stack segment的長度(block)
(4) sep——是否“i”、“d”分割槽。(我們只探討sep為0的情況)
1650: estabur(nt, nd, ns, sep)
1651: {
1652: register a, *ap, *dp;
1653:
1654: if(sep) {
……
1659: } else
1660: if(nseg(nt)+nseg(nd)+nseg(ns) > 8) /函式nseg會將block數轉化為page數,
1661: goto err; /1 page最多128個block,程序user態最多8個page
1662: if(nt+nd+ns+USIZE > maxmem)
1663: goto err;
1664: a = 0; /uisa[]從0開始計數,單位是block
1665: ap = &u.u_uisa[0];
1666: dp = &u.u_uisd[0];
1667: while(nt >= 128) { /對映text segment
1668: *dp++ = (127<<8) | RO; /1
1669: *ap++ = a; /text segment是隻讀的,故為RO
1670: a =+ 128;
1671: nt =- 128;
1672: }
1673: if(nt) { /不夠128個block的部分,
1674: *dp++ = ((nt-1)<<8) | RO; /也必須對映到一個單獨的page上
1675: *ap++ = a;
1676: }
1677: if(sep)
……
1681: }
1682: a = USIZE; /a回滾了,與Text segment的設定有重疊
1683: while(nd >= 128) {
1684: *dp++ = (127<<8) | RW; /除了狀態為RW外,其他設定與text類似
1685: *ap++ = a;
1686: a =+ 128;
1687: nd =- 128;
1688: }
1689: if(nd) {
1690: *dp++ = ((nd-1)<<8) | RW;
1691: *ap++ = a;
1692: a =+ nd;
1693: }
1694: while(ap < &u.u_uisa[8]) { /剩下的page清0
1695: *dp++ = 0;
1696: *ap++ = 0;
1697: }
1698: if(sep)
……
1702: }
1703: a =+ ns; /a指向stack segment的最後
1704: while(ns >= 128) {
1705: a =- 128; /設定stack segment
1706: ns =- 128;
1707: *--dp = (127<<8) | RW;
1708: *--ap = a;
1709: }
1710: if(ns) {
1711: *--dp = ((128-ns)<<8) | RW | ED;
1712: *--ap = a-128;
1713: }
1714: if(!sep) { /如未進行“i”、“d”分割槽
1715: ap = &u.u_uisa[0];
1716: dp = &u.u_uisa[8];
1717: while(ap < &u.u_uisa[8]) /則uisa[8~15] = uisa[0~7]
1718: *dp++ = *ap++;
1719: ap = &u.u_uisd[0];
1720: dp = &u.u_uisd[8];
1721: while(ap < &u.u_uisd[8]) /則uisd[8~15] = uisd[0~7]
1722: *dp++ = *ap++;
1723: }
1724: sureg(); /稍後討論
1725: return(0);
1726:
1727: err:
1728: u.u_error = ENOMEM;
1729: return(-1);
1730: }
從程式碼中得知:
(1)unix會將text、data、stack對映到不同的page上去;
(2)uisd[]中記錄的似乎就是相應的page description register的值;
(3)uisa[]卻不是page address register的值—— text和data segment有重疊。
它是什麼呢?我們接著往下看。
sureg()會根據u_uisa[]和u_uisd[]陣列來設定相應的UISA和UISD register,通過這個函式,
我們可以清楚的知道u_uisa[]和u_uisd[]陣列的真正含義。
1739: sureg()
1740: {
1741: register *up, *rp, a;
1742:
1743: a = u.u_procp->p_addr; /a為程序私有空間的開始block號
1744: up = &u.u_uisa[16];
1745: rp = &UISA->r[16];
1746: if(cputype == 40) { /PDP-11/40,未進行“i”、“d”分割槽
1747: up -= 8; /故,u_uisa[8~15]是無效的
1748: rp -= 8;
1749: }
1750 while(rp > &UISA->r[0]) /UISA[ ] = u_uisa[ ] + a
1751: *--rp = *--up + a;
1752: if((up=u.u_procp->p_textp) != NULL) /如text segment已經分配了地址,text segment
1753: a -= up->x_caddr; /需要特殊調整。up->x_caddr為text segment的
1754: up = &u.u_uisd[16]; /core load地址,對#1程序,a值未變(可認其
1755: rp = &UISD->r[16]; /為core load地址為0)
1756: if(cputype == 40) {
1757: up -= 8;
1758: rp -= 8;
1759: }
1760: while(rp > &UISD->r[0]) {
1761: *--rp = *--up; /UISA[ ] = u_uisd[ ]
1762: if((*rp & WO) == 0) /只有text segment是不可寫的
1763: rp[(UISA-UISD)/2] -= a; /重新調整text segment的page address register
1764: } /rp現在指向UISD,通過UISA和UISD地址計算
1765: } /出偏移量,直接通過rp操作UISA
顯然:
(1)u_uisd[]中記錄的就是相應的page description register的值;
(2)data segment和stack segment均被對映到程序的“私有空間”的PPDA之後;
對這兩種segment,u_uisa[](protype)記錄的為相對於PPDA的偏移block數;
【注】:這兩個user態的segment被對映到程序的“私有空間”之後,也變成了程序的“私有空間”的一
部分。也就是說,程序擁有連續的私有空間,且可以分為兩部分:一部分在kernel使用,一部分
在user態使用。連續的空間為swap提供了方便。
(3)text segment如已有地址,則從原來地址開始對映,否則(如我們的#1程序),從第0個block開始對映。
對text segment,u_uisa[]記錄的為相對於core Load地址(#1程序為0)的偏移block數。
u中還有一些變數與這三個segment有關,如:
0440: int u_tsize; /* text size (*64) */ 記錄的是block數
0441: int u_dsize; /* data size (*64) */
0442: int u_ssize; /* stack size (*64) */
【protype再分析】:
為什麼會設計protype Address呢?很簡單,程序在其生存期內是有可能被換出記憶體的,當再次被換進記憶體時,
它佔據的物理位置就會發生變化——而其protype Address不變。這樣通過呼叫sureg()就可以方便的設定好
user Address register。