GUI系統之SurfaceFlinger(15)handlePageFlip
轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/
GUI系統之SurfaceFlinger章節目錄:
blog.csdn.net/uiop78uiop78/article/details/8954508
PageFlip可以理解為“翻頁”。從這個意思上來看,它應該與圖層緩衝區有關係——因為是多緩衝機制,在適當的時機,我們就需要做“翻頁”的動作。
void SurfaceFlinger::handlePageFlip()
{…
const DisplayHardware&hw = graphicPlane(0).displayHardware();//編號為0的Display
const RegionscreenRegion(hw.bounds());//整個螢幕區域
const LayerVector¤tLayers(mDrawingState.layersSortedByZ);/*當前所有layers*/
const bool visibleRegions = lockPageFlip(currentLayers);/*Step1.下面會詳細分析這個函式,注意它的返回
值是一個bool型別變數。*/
if (visibleRegions ||mVisibleRegionsDirty) {//可見區域發生變化
RegionopaqueRegion;//不透明區域
computeVisibleRegions(currentLayers, mDirtyRegion, opaqueRegion);/*Step2.計算可見區域*/
/*Step3.重建mVisibleLayersSortedByZ,即所有可見圖層的排序*/
const size_t count= currentLayers.size();
mVisibleLayersSortedByZ.clear();//清空
mVisibleLayersSortedByZ.setCapacity(count);//容量
for (size_t i=0 ;i<count ; i++) {
if(!currentLayers[i]->visibleRegionScreen.isEmpty())//當前圖層有可見區域
mVisibleLayersSortedByZ.add(currentLayers[i]);
}
mWormholeRegion = screenRegion.subtract(opaqueRegion);/*Step4.蟲洞計算*/
mVisibleRegionsDirty = false;
invalidateHwcGeometry();
}
unlockPageFlip(currentLayers);/*Step5.與lockPageFlip相對應 */
…
mDirtyRegion.andSelf(screenRegion);//排除螢幕範圍之外的區域
}
[email protected]::handlePageFlip,通過lockPageFlip分別鎖定各layer當前要處理的緩衝區。SurfaceFlinger::lockPageFlip邏輯比較簡單,我們直接來看下Layer::lockPageFlip:
void Layer::lockPageFlip(bool& recomputeVisibleRegions)
{…
if (mQueuedFrames > 0) {…
if(android_atomic_dec(&mQueuedFrames) > 1) {
mFlinger->signalLayerUpdate();
}
…
Rejectr(mDrawingState, currentState(), recomputeVisibleRegions);
if (mSurfaceTexture->updateTexImage(&r) < NO_ERROR) {//載入新的紋理
recomputeVisibleRegions = true;
return;
}
mActiveBuffer = mSurfaceTexture->getCurrentBuffer();//當前活躍的緩衝區
…//其它內部變數更新
glTexParameterx(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);//配置引數
glTexParameterx(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
SurfaceTexture設定了一個FrameQueuedListener,當BufferQueue中的一個BufferSlot被queued後,它首先通知這個Listener,進而呼叫所屬Layer的onFrameQueued。這個函式會增加mQueuedFrames的計數,並且向SurfaceFlinger發出一個invalidate訊號(signalLayerUpdate)。
因而如果當前沒有任何queuedbuffer的話,lockPageFlip()什麼都不用做。假如當前有多個mQueuedFrames的話,除了正常處理外,我們還需要另外呼叫signalLayerUpdate來發出一個新的event。
Layer中持有一個SurfaceTexture物件(成員變數mSurfaceTexture),用於管理BufferQueue(成員變數mBufferQueue)。一旦SurfaceFlinger需要對某個buffer進行操作時,根據前幾個小節對BufferQueue狀態遷移的分析,我們知道它首先要accquire它,這個動作被封裝在SurfaceTexture::updateTexImage中。除此之外,這個函式還需要根據Buffer中的內容來更新Texture,稍後我們做詳細分析。
在lockPageFlip內部,定義了一個Reject結構體,並做為函式引數傳遞給updateTexImage。後者在acquire到buffer後,呼叫Reject::reject()來判斷這個緩衝區是否符合要求。如果updateTexImage成功的話,Layer就可以更新mActiveBuffer,也就是當前活躍的緩衝區(每個layer對應32個BufferSlot);以及其它一系列相關內部成員變數,比如mCurrentCrop、mCurrentTransform等等,這部分原始碼省略。
紋理貼圖還涉及到很多細節的配置,比如說紋理影象與目標的尺寸大小有可能不一致,這種情況下怎麼處理?或者當紋理座標超過[0.0,1.0]範圍時,應該怎麼解決。這些具體的處理方式都可以通過呼叫glTexParameterx(GLenumtarget, GLenum pname, GLfixed param)來配置。函式的第二個引數是需要配置的物件型別(比如GL_TEXTURE_WRAP_S和GL_TEXTURE_WRAP_T分別代表兩個座標維);第三個就是具體的配置。
status_t SurfaceTexture::updateTexImage(BufferRejecter* rejecter) {…
Mutex::Autolocklock(mMutex);
status_t err = NO_ERROR;
…
EGLDisplay dpy =eglGetCurrentDisplay();//當前Display
EGLContext ctx =eglGetCurrentContext();//當前環境Context
…
BufferQueue::BufferItemitem;
err = mBufferQueue->acquireBuffer(&item);//acquire當前需要處理的buffer,封裝在item中
if (err == NO_ERROR) {//acquire成功
int buf = item.mBuf; //此Buffer在Slot中的序號
if(item.mGraphicBuffer != NULL) {…
mEGLSlots[buf].mGraphicBuffer = item.mGraphicBuffer;//後面會用到
}
…
EGLImageKHR image =mEGLSlots[buf].mEglImage;
if (image ==EGL_NO_IMAGE_KHR) {/*假如image不為空的話,前面會先把它destroy掉,程式碼
省略*/
if(mEGLSlots[buf].mGraphicBuffer == NULL) {
…//異常處理
} else {
image = createImage(dpy, mEGLSlots[buf].mGraphicBuffer);//生成一個紋理影象
mEGLSlots[buf].mEglImage = image;
…
}
}
if (err == NO_ERROR) {…
glBindTexture(mTexTarget, mTexName);
glEGLImageTargetTexture2DOES(mTexTarget, (GLeglImageOES)image);
…
}
if (err != NO_ERROR) {//將acquired的buffer釋放
mBufferQueue->releaseBuffer(buf, dpy, mEGLSlots[buf].mFence);
mEGLSlots[buf].mFence = EGL_NO_SYNC_KHR;
return err;
}
…
/*最後更新SurfaceTexture中各狀態*/
mCurrentTexture = buf;
mCurrentTextureBuf =mEGLSlots[buf].mGraphicBuffer;
…
} else {…
}
return err;
}
這個函式比較長,我們只留下了最核心的部分。它的目標是更新Layer的紋理(Texture),分為如下幾步:
①Acquire一個需要處理的Buffer。根據前面對Buffer狀態遷移的分析,當消費者想處理一塊buffer時,它首先要向BufferQueue做acquire申請。那麼BufferQueue怎麼知道當前要處理哪一個Buffer呢?這是因為其內部維護有一個Fifo先入先出佇列。一旦有buffer被enqueue後,就會壓入隊尾;每次acquire就從隊頭取最前面的元素進行處理,完成之後就將其從佇列移除
②Acquire到的buffer封裝在BufferItem中,item.mBuf代表它在BufferSlot中的序號。正常情況下item.mGraphicBuffer都不為空,我們將它記錄到mEGLSlots[buf].mGraphicBuffer中,以便後續操作。假如mEGLSlots[buf].mEglImage當前不為空(EGL_NO_IMAGE_KHR)的話,需要先把舊的image銷燬(eglDestroyImageKHR)
③SurfaceFlinger有權決定SurfaceTexture得到的Buffer是否有效合法(比如說size是否正確),這是通過updateTexImage(BufferRejecter* rejecter)中的rejecter物件來完成的。BufferRejecter實現了一個reject介面,用於SurfaceTexture呼叫來驗證前面acquire到的buffer是否符合要求
④接下來就生成Texture了。需要分別呼叫glBindTexture和 glEGLImageTargetTexture2DOES。後一個函式是opengl es中對glTexImage2D的擴充套件,因為在嵌入式環境下如果直接採用這個API的話,當圖片很大時會嚴重影響到速度,而經過擴充套件後的glEGLImageTargetTexture2DOES可以解決這個問題。這個介面是和eglCreateImageKHR配套使用的,封裝在前面createImage中。不瞭解這些函式用法的讀者請務必參考opengles技術文件
⑤消費者一旦處理完Buffer後,就可以將其release了。此後這個buffer就又恢復FREE狀態,以供生產者再次dequeue使用
⑥最後,我們需要更新SurfaceTexture中的各成員變數,包括mCurrentTexture、mCurrentTextureBuf、mCurrentCrop等等
圖 11‑38 lockPageFlip流程圖
[email protected]::handlePageFlip,計算所有layer的可見區域。在分析原始碼前,我們自己先來想一下,圖層中什麼樣的區域是可見的呢?
l Z-order
各layer的z-order無疑是第一考慮的要素。因為排在越前面的圖層,其獲得曝光的機率越大,可見的區域也可能越大,如下圖所示:
圖 11‑39 後面的圖層有可能被遮擋而不可見
所以在計算可見性時,是按照Z-order由上而下進行的。假如一個layer的某個區域被確定為可見,那麼與之相對應的它下面的所有圖層區域都會被遮蓋而不可見
l 透明度
雖然越前面的layer優先順序越高,但這並不代表後面的圖層完全沒有機會。只要前一個layer不是完全不透明的,那麼從理論上來講使用者就應該能“透過”這部分割槽域看到後面的內容
l 圖層大小
與透明度一樣,圖層大小也直接影響到其可見區域。因為每個layer都是有大有小的,即便前一個layer是完全不透明的,但只要它的尺寸沒有達到“滿屏”,那麼比它z-order小的圖層還是有機會暴露出來的。這也是我們需要考慮的因素之一
綜合上面的這幾點分析,我們能大概制定出計算layer可見區域的邏輯步驟:
Ø 按照Z-order逐個計算各layer的可見區域,結果記錄在LayerBase::visibleRegionScreen中
Ø 對於Z-order值最大的layer,顯然沒有其它圖層會遮蓋它。所以它的可見區域(visibleRegion)應該是(當然,前提是這個layer沒有超過螢幕區域)自身的大小再減去完全透明的部分(transparentRegionScreen),由此計算出來的結果我們把它稱為aboveCoveredLayers。這個變數應該是全域性的,因為它需要被傳遞到後面的layers中,然後不斷地累積運算,直到覆蓋整個螢幕區域
圖 11‑40 Z-order最大的layer可見區域示意圖
如上圖所示,外圍加深部分是這個圖層的尺寸大小,中間挖空區域則是完全透明的,因而需要被剔除。半透明區域比較特殊,它既屬於上一個圖層的可見區域,又不被列為遮蓋區域
Ø 對於Z-order不是最大的layer,它首先要計算自身所佔區域扣除aboveCoveredLayers後所剩的空間。然後才能像上一步一樣再去掉完全透明的區域,這樣得到的結果就是它最終的可見區域
圖 11‑41 其它layer的可見區域
接下來看原始碼實現:
void SurfaceFlinger::computeVisibleRegions(const LayerVector¤tLayers,
Region& dirtyRegion, Region& opaqueRegion)
{…
const GraphicPlane&plane(graphicPlane(0));
const Transform&planeTransform(plane.transform());
const DisplayHardware&hw(plane.displayHardware());
const RegionscreenRegion(hw.bounds());//整個螢幕區域
Region aboveOpaqueLayers;
Region aboveCoveredLayers;//全域性的,用於描述當前已經被覆蓋的區域
Region dirty;
bool secureFrameBuffer =false;
size_t i =currentLayers.size();//所有layer數量
while (i--) {/Step1. 注意計算的方向,按照Z-order由大到小的順序
constsp<LayerBase>& layer = currentLayers[i];//取得這一layer
layer->validateVisibility(planeTransform);//驗證當前layer的可見性,大家可以自行分析
constLayer::State& s(layer->drawingState());//layer的狀態
Region opaqueRegion;//完全不透明的區域
Region visibleRegion;//可見區域
Region coveredRegion;//被遮蓋的區域。以上三個變數都是區域性的,用於描述各個layer
if(CC_LIKELY(!(s.flags & ISurfaceComposer::eLayerHidden) && s.alpha)){
/*Step2. 這部分計算可見區域visibleRegion和完全不透明區域opaqueRegion*/
const booltranslucent = !layer->isOpaque(); //layer可見,又不是完全不透明,那就是半透明
const Rectbounds(layer->visibleBounds());
visibleRegion.set(bounds);//可見區域的初始值
visibleRegion.andSelf(screenRegion);//和螢幕大小先進行與運算
if(!visibleRegion.isEmpty()) {//如果經過上述運算,可見區域還不為空的話
if(translucent) { //將完全透明區域從可見區域中移除
visibleRegion.subtractSelf(layer->transparentRegionScreen);
}
const int32_tlayerOrientation = layer->getOrientation();//旋轉角度
if(s.alpha==255 && !translucent &&
((layerOrientation & Transform::ROT_INVALID) == false)) {
// theopaque region is the layer's footprint
opaqueRegion = visibleRegion;//完全一致
}
}
}
/*Step3. 考慮被上層layer覆蓋區域*/
coveredRegion = aboveCoveredLayers.intersect(visibleRegion);//計算會被遮蓋的區域
aboveCoveredLayers.orSelf(visibleRegion);//累加計算,然後傳給後面的layer
visibleRegion.subtractSelf(aboveOpaqueLayers);//扣除被遮蓋的區域
/*Step4. 計算“髒”區域*/
if(layer->contentDirty) {//當前layer有髒內容
dirty =visibleRegion;//不光要包括本次的可見區域
dirty.orSelf(layer->visibleRegionScreen);//還應考慮上一次沒處理的可見區域
layer->contentDirty = false;//處理完成
} else {//當前layer沒有髒內容
const RegionnewExposed = visibleRegion - coveredRegion;
const RegionoldVisibleRegion = layer->visibleRegionScreen;
const RegionoldCoveredRegion = layer->coveredRegionScreen;
const RegionoldExposed = oldVisibleRegion - oldCoveredRegion;
dirty =(visibleRegion&oldCoveredRegion) | (newExposed-oldExposed);
}
/*Step5. 更新各變數*/
dirty.subtractSelf(aboveOpaqueLayers);//扣除不透明區域
dirtyRegion.orSelf(dirty);//累積計算髒區域
aboveOpaqueLayers.orSelf(opaqueRegion);//累積不透明區域
layer->setVisibleRegion(visibleRegion);//設定layer內部的可見區域
layer->setCoveredRegion(coveredRegion);//設定layer內部的被遮蓋區域
/*Step6. 螢幕截圖保護*/
if(layer->isSecure() && !visibleRegion.isEmpty()) {
secureFrameBuffer= true;
}
}//while迴圈結束
/*Step7. 考慮被移除layer佔據的區域*/
dirtyRegion.orSelf(mDirtyRegionRemovedLayer);
mDirtyRegionRemovedLayer.clear();
mSecureFrameBuffer =secureFrameBuffer;
opaqueRegion =aboveOpaqueLayers;
}
[email protected] SurfaceFlinger::computeVisibleRegions,迴圈處理每個layer。注意計算方向(i--)採取Z-order由大而小的順序,這和我們之前的分析是一致的。這個函式中定義了很多變數,包括對各layer全域性的aboveOpaqueLayers、aboveCoveredLayers、dirty,以及區域性的opaqueRegion、visibleRegion、coveredRegion,但基本邏輯還是和我們前面的推測相符合的。
[email protected] SurfaceFlinger::computeVisibleRegions,計算visibleRegion和opaqueRegion。不過有兩個情況下不需要這麼做。其一就是當前layer被隱藏了,這可以從s.flags & ISurfaceComposer::eLayerHidden中獲得;其二就是它的alpha值為0,也就是說當前layer是完全透明的。
如果不是這兩種情況的話,就可以繼續計算visibleRegion。它的初始值是layer->visibleBounds(),相當於前面我們說的該layer所佔據的區域。這一初始值與螢幕區域進行與運算,排除螢幕顯示區域外的部分。此時如果visibleRegion還有剩餘空間的話,就繼續減掉透明區域。
變數opaqueRegion在某些情況下和visibleRegion是一樣的。即當前圖層完全不透明(alpha==255&&!translucent)且旋轉角度值為ROT_INVALID時,兩者可認為是同一個區域。
[email protected] SurfaceFlinger::computeVisibleRegions,考慮被上層layer遮蓋的情況。其中coveredRegion計算得到會被遮蓋的區域,即visibleRegion需要剔除的部分。並且累加更新全域性的aboveCoveredLayers,以便後面的layer可以繼續使用。
[email protected] SurfaceFlinger::computeVisibleRegions,在前面的基礎上進一步計算dirty區域,也就是需要渲染的部分。涉及到兩個變數,dirty是一個全部變數,它表示每個layer中的髒區域;而dirtyRegion是函式的出參,屬於全域性性的,用於計算所有layer髒區域的集合(採用“或”運算)。可能有人會問,需要渲染的區域不就是可見區域嗎?這可不一定。比如說當前介面內容沒有任何改變,那麼為什麼還要浪費時間再重新渲染一次呢?
如果有“髒”內容(layer->contentDirty為true),dirty要同時覆蓋當前可見區域(visibleRegion),以及上次還未考慮的可見區域(layer->visibleRegionScreen)。
如果沒有“髒”內容,那麼我們只要渲染這次新“暴露”出來的區域就可以了。因為上一次暴露出來的區域已經被渲染過了,而且內部又沒有變化,當然不需要再次渲染。
[email protected] SurfaceFlinger::computeVisibleRegions,更新所有相關變數,包括layer內部的visibleRegionScreen(通過setVisibleRegion)、coveredRegionScreen(通過setCoveredRegion),以及需要累積計算的aboveOpaqueLayers,和dirtyRegion等等。
[email protected] SurfaceFlinger::computeVisibleRegions,判斷當前視窗內容是否受保護(變數secureFrameBuffer)。判斷的標準就是當前可見區域不為空(!visibleRegion.isEmpty()),且需要安全保護(isSecure()),這將影響到截圖功能是否可以正常執行。
[email protected] SurfaceFlinger::computeVisibleRegions,除了上述的步驟外,我們還要考慮那些被移除的layer所佔據的區域,這樣得到的才是最終的結果。函數出參opaqueRegion也就是通過各layer計算後的累加值aboveOpaqueLayers。
分析完可見區域的計算後,我們回到本小節的handlePageFlip函式中。
[email protected]::handlePageFlip. 通過前面的computeVisibleRegions,現在所有layer的可見區域都已經記錄在其內部的visibleRegionScreen中了。接下來 mVisibleLayersSortedByZ用於對所有可見的圖層進行z-order排序。
[email protected]::handlePageFlip. 在computeVisibleRegions中,opaqueRegion做為所有layer的遮擋區域(對應aboveOpaqueLayers)的累積結果,正常情況下應該是和螢幕大小是一致的。反之,如果它們不一樣的話,就產生了wormhole區域:
mWormholeRegion = screenRegion.subtract(opaqueRegion);
這部分割槽域比較特殊,因而在後續的圖層合成時需要做額外處理(具體呼叫drawWormhole來實現)。
[email protected]::handlePageFlip,呼叫unlockPageFlip做最後的準備工作。實現上和lockPageFlip有相似之處,也是通過迴圈呼叫各layer自己的unlockPageFlip來完成。因為圖層“髒”區域的座標空間和螢幕座標不是同一個,在這裡要對其做下座標變換。
相關推薦
GUI系統之SurfaceFlinger(15)handlePageFlip
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
android Gui系統之SurfaceFlinger(5)---Vsync(2)
9.Vsync第二部分 在上一篇中我們講到,檢視的重新整理需要很多步驟, void SurfaceFlinger::handleMessageRefresh() { ATRACE_CALL(); preComposition(); //合成前的準備 rebui
android Gui系統之SurfaceFlinger(4)---Vsync(1)
8.Vsync 回到頂部 8.1概論 VSYNC(Vertical Synchronization)是一個相當古老的概念,對於遊戲玩家,它有一個更加大名鼎鼎的中文名字—-垂直同步。 “垂直同步(vsync)”指的是顯示卡的輸出幀數和螢幕的垂直重新整理率相同,這完全是一個CRT顯示器上
android Gui系統之SurfaceFlinger(3)---SurfaceFlinger
7.SurfaceFlinger SurfaceFlinger在前面的篇幅了,多有涉及。 SurfaceFlinger是GUI重新整理UI的核心,所以任何關於SurfaceFlinger的改進都會對android UI系統有重大影響。 SurfaceFlinger主要分為4個部分 1
android Gui系統之SurfaceFlinger(2)---BufferQueue
6 BufferQueue 上一篇已經說到,BufferQueue是SurfaceFlinger管理和消費surface的中介,我們就開始分析bufferqueue。 每個應用 可以由幾個BufferQueue? 應用繪製UI 所需的記憶體從何而來? 應用和SurfaceFlinge
android Gui系統之SurfaceFlinger(1)---SurfaceFlinger概論
GUI 是任何系統都很重要的一塊。 android GUI大體分為4大塊。 1)SurfaceFlinger 2)WMS 3)View機制 4)InputMethod 這塊內容非常之多,但是理解後,可以觸類旁通,其實現在主流的系統,包括andorid,ios
android Gui系統之SurfaceFlinger(1)---SurfaceFlinger概論【轉】
轉自:https://www.cnblogs.com/deman/p/5584198.html 閱讀目錄 1.OpenGL & OpenGL ES 2.Android的硬體介面HAL 3.Android顯示裝置:Gralloc & FrameBuff
GUI系統之SurfaceFlinger(18)postFramebuffer
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
GUI系統之SurfaceFlinger(12)VSync訊號的產生和處理
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
ubantu系統之安裝notepadqq
get team repos sta all update apt apt-get ubantu Ubuntu下的安裝方法: sudo add-apt-repository ppa:notepadqq-team/notepadqq sudo apt-get
天津政府應急系統之GIS一張圖(arcgis api for flex)解說(三)顯示地圖坐標系模塊
image blur rda plain 讀取 else important baseline pat config.xml文件的配置例如以下: 1 2 <widget left="3" bottom="3" config="widg
推薦系統之冷啟動問題
tail 高效 排行榜 音樂 復雜 技術 一個 ora tle 轉自http://blog.csdn.net/zhangjunjie789/article/details/51379127 如何在沒有大量用戶數據的情況下設計個性化推薦系統並且讓用戶對推薦結果滿意從而願意使用
訂餐系統之同步美團商家訂單
ken first ret 沒有 warn ons 回歸 http 選擇 引子 早上和往常一樣去工商大學打球,除了今天三分比較準外,一切都還是那樣的循規蹈矩。 也許股子裏還有那麽一些不甘平庸,總想著能改變一下如此無趣的按部就班。 轉過行政樓
Net分布式系統之六:微服務之API網關
pan 業務邏輯 nginx clas 系統 gin 性能 blog services 本人建立了個人技術、工作經驗的分享微信號,計劃後續公眾號同步更新分享,比在此更多具體。歡迎有興趣的同學一起加入相互學習。基於上篇微服務架構分享,今天分享其中一個重要的基礎組件&ldq
客戶管理系統之模塊設計(八)
說了 sender windows .net children comm eve exceptio client 2,加入信息投訴和改動投訴信息模塊 關於投訴信息的加入模塊和改動模塊均使用的是一個窗口,其其差別是依據向窗口中所傳遞的參數
如何選擇版本控制系統之二---Git的研發應用場
nal fix idt max-width 設計理念 ted 過程 style ria 之前寫了一篇《如何選擇版本控制系統 ---為什麽選擇Git版本控制系統》,地址是:http://www.cnblogs.com/goldenfish/p/6876864.html,有興趣
如何選擇版本控制系統之二---Git的研發應用場景
版本控制系統 git 華為軟件開發雲 之前寫了一篇《如何選擇版本控制系統 ---為什麽選擇Git版本控制系統》,地址是:http://laoyudage.blog.51cto.com/12854334/1927409,有興趣的可以去看看,本篇文章算是這個系列的第二篇文章。Git誕生於2002年,由
Python第5堂課(linux系統之用戶群組權限篇)
用戶添加 -s 用戶和組 alex 更改 讀取 ... 一個 顯示 作業一: 1) 新建用戶natasha,uid為1000,gid為555,備註信息為“master” 2) 修改natasha用戶的家目錄為/Natasha 3) 查看用戶信息配置文件的最後一行 4) 為n
Linux系統之文件共享NFS
network 配置文件 本地磁盤 局域網 服務器 實驗 一、基礎概念NFS:Network File System網絡文件系統,Unix系統之間共享文件的一種協議,只能應用在局域網中 將網絡磁盤分區映射到本地,節省本地磁盤空間 協議: RPC(Remote Proced
python編程快速上手之第15章實踐項目參考答案(17.7.2)
col ges code sid documents mod 編程 bsp tof #! python3 # Import modules and write comments to describe this program. import zipfile, os fr