• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            T9的空間

            You will never walk alone!

              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
              69 隨筆 :: 0 文章 :: 28 評論 :: 0 Trackbacks

            #

            線程控制

            APUE講的destroy會free空間,這件事情看起來不太對,也許是Base on實現(xiàn)
            起碼我看到的實現(xiàn)是沒有做這件事情的。

             

            int pthread_attr_init(pthread_attr_t * attr)
            {
                
            *attr = gDefaultPthreadAttr;
                
            return 0;
            }


            int pthread_attr_destroy(pthread_attr_t * attr)
            {
                memset(attr, 
            0x42sizeof(pthread_attr_t));
                
            return 0;
            }


            Attribution有下面這些。

             

            typedef struct
            {
                uint32_t flags;
                
            void * stack_base;
                size_t stack_size;
                size_t guard_size;
                int32_t sched_policy;
                int32_t sched_priority;
            }
             pthread_attr_t;

            我們可以用malloc和mmap給Thread分配stack,指定stack大小和位置(低地址)

            mutex屬性
            兩個東西
            1.進程共享屬性,PTHREAD_PROCESS_PRIVATE or SHARED
            意思是說如果這個Mutex是分配在兩個進程共享的memory,然后設置為shared mutex就可以用于進程同步
            2.類型屬性,normal;errorcheck;recurive
            意思是定義Mutex本身的特性,這里更正下前面那章提到的錯誤
            只有normal的mutex才會在再次加鎖時發(fā)生deadlock
            errorcheck的會直接返回錯誤
            recurive本身就允許,會有計數(shù)

            rw lock和condition屬性
            只支持第一個

            TLS變量
            先看下TLS的位置

             * +---------------------------+
             
            * |     pthread_internal_t    |
             
            * +---------------------------+
             
            * |                           |
             
            * |          TLS area         |
             
            * |                           |
             
            * +---------------------------+
             
            * |                           |
             
            * .                           .
             
            * .         stack area        .
             
            * .                           .
             
            * |                           |
             
            * +---------------------------+
             
            * |         guard page        |
             
            * +---------------------------+

            pthread_internal_t是記錄線程自己本身的一些屬性的變量,guard page就是線程attr上設置的防止棧溢出的擴展內(nèi)存

            創(chuàng)建TLS變量的方法
            1.創(chuàng)建Key,這個Key是這個Process中所有Thread可以共享訪問的,然后每個Thread自己去關聯(lián)自己的Thread Local變量
            int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void*))
            創(chuàng)建key的時候需要保證唯一調(diào)用,也就是不會有兩個Thread同時調(diào)用,然后發(fā)生一些base on實現(xiàn)的不確定的結(jié)果,可以使用pthread_once

            pthread_once的實現(xiàn)很簡單,用mutex做互斥量保證pthread_once_t的訪問,然后利用pthread_once_t做flag來調(diào)用init_routine

            int  pthread_once( pthread_once_t*  once_control,  void (*init_routine)(void) )
            {
                
            static pthread_mutex_t   once_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
                
            volatile pthread_once_t* ocptr = once_control;

                pthread_once_t tmp 
            = *ocptr;
                ANDROID_MEMBAR_FULL();
                
            if (tmp == PTHREAD_ONCE_INIT) {
                    pthread_mutex_lock( 
            &once_lock );
                    
            if (*ocptr == PTHREAD_ONCE_INIT) {
                        (
            *init_routine)();
                        ANDROID_MEMBAR_FULL();
                        
            *ocptr = ~PTHREAD_ONCE_INIT;
                    }

                    pthread_mutex_unlock( 
            &once_lock );
                }

                
            return 0;
            }

            所以這個pthread_once_t必須是全局or靜態(tài)變量
            pthread_once最常用的case除了拿到TLS key外,init函數(shù)經(jīng)常會使用這個,避免多線程同時調(diào)用。

            然后就是thread cancel
            Android Bionic庫沒有實現(xiàn) pthread_setcancelstate(int state, int* oldstate)以及pthread_cancel()
            這里thread的取消,有些是取消不掉的,例如一些blocking call,所有的取消均是需要設定一些取消點的,

            有人有專門針對Android沒有Thread Cancel來想辦法
            基本就是thread signal,在signal handler中去call pthread_exit();
            or用PIPE在多路復用中break出來
            http://blog.csdn.net/langresser/article/details/8531112

            如果信號與硬件故障or計時器超時相關,那么信號會發(fā)送到引起事件的該線程中去,其他信號會發(fā)送到任意一個線程。
            這里是說的POSIX線程模型,一般的信號都會發(fā)送給進程中沒有阻塞該信號的某個線程。APUE講的某個沒有阻塞該信號的線程,其實有點模糊。

            但是Linux的實現(xiàn)不一樣,linux的thread是clone出來的,就是共享資源的獨立進程,這樣子其實蠻自然的,也不容易復雜。
            linux中,有可能線程就不會注意到該信號,這樣講也有點模糊,總之終端驅(qū)動程序的信號會將信號通知到進程組,這樣子所有的thread就會都收到信號
            如果你不想所有的線程都去關心信號,那么可以使用一個專門處理信號的線程
            先將其他線程的signal都pthread_sigmask SIG_BLOCK掉,然后使用sigwait在特定的線程來等
            pthread_sigmask和sigprocmask很像

            thread與fork
            fork后的子進程會繼承mutex, rw lock, condition的狀態(tài)
            但是子進程中只會存在一個線程,也就是那個調(diào)用fork的線程的副本,有個復雜的事情是關于上面繼承下來的那些東西,子進程由于只有一個線程,沒辦法知道
            上面那三個東西的具體狀態(tài),需要有個清理鎖的動作 -->pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
            我目前不知道這個東西的使用場景,而且不容易控制。
            因為一般如果我們fork后都會接execu,這樣地址空間就會改變,那么這些繼承的東西就沒用了。

            然后有談一點關于線程IO的東西
            pwrite/pread
            也就是原子的進行l(wèi)seek和r/w,但是這個并不能保證大家常見的沖突問題,也就是一個thread在寫另外一個要讀,所以這兩個東西并不能解決同步的問題。

            作業(yè):
            12.3 理論上函數(shù)開始時屏蔽所有信號,函數(shù)結(jié)束時恢復,是可以做到異步信號安全的。如果你只是屏蔽一些信號,那么沒辦法做到,因為如果有其他信號進來,你call的函數(shù)里面有一些不可重入的函數(shù),同樣不能保證是異步安全的。

            12.5 fork可以在拿來執(zhí)行可執(zhí)行程序,現(xiàn)在應該就這一個Case。

            posted @ 2013-06-04 15:41 Torres 閱讀(276) | 評論 (0)編輯 收藏

            線程函數(shù)
            int pthread_equal(pthread_t tid1, pthread_t tid2)
            pthread_t pthread_self(void)

            int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr
                 void* (*start_rtn)(void), void* restrict arg)

            thread被創(chuàng)建的時候會繼承調(diào)用線程的浮點環(huán)境和信號屏蔽字,但是該線程的未決信號集將會被清楚,
            清除未決信號集這件事情應該是沒有疑問的,在thread創(chuàng)建之前pending住的信號,不應該deliver給下一個
            Ps: 線程的浮點環(huán)境指的是啥? 看來以后我應該去注意下浮點數(shù)的運算原理。

            pthread相關的函數(shù)會直接返回錯誤碼,而不會和一些system call一樣,置全局errno,兩種方式都有好處,一個可以講返回值
            帶回的錯誤代碼集中起來,范圍縮小;另外一個非常方便,關鍵點在于這一類共用errno的是否真的異常是可以共用的。

            pthread_create返回之前有可能新的線程就已經(jīng)開始run了

            啟動函數(shù) void* (*start_rtn)(void)

            可以通過return給回來,也可以通過pthread_exit給
            這個會存在一個地方
            通過pthread_join(tid, void**)取回來

            這里join的和java join是一樣的功能

            如果這個東西是一個很大的東西:),那么放到heap是最好的選擇,不要放到stack上了,不然線程返回這東西就沒了,join取到的內(nèi)存地址就變成一個無效的了,SIGSEGV就會被發(fā)出來

            pthread_cancel,同一個進程可以call,提出請求終止線程

            pthread_cleanup_push
            pthread_cleanup_pop

            線程分離,這樣子線程終止后可以釋放一些資源,而不用一定要其他人來join
            方法有兩種,新建的時候加上分離屬性
                pthread_attr_init (&attr);
                pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);

            或者call pthread_detach(pthread_t tid)

            線程互斥與同步

            typedef struct
            {
                
            int volatile value;
            }
             pthread_mutex_t;

            多注意volatile變量,這個東西理論上就是讓編譯器不要做優(yōu)化,不要cache volatile類型的變量,
            每次都去內(nèi)存地址中拿,而不是寄存器/高速緩存副本,這種變量極容易被編譯器不知道的人改變,例如其他線程。

            靜態(tài)初始化:
            #define  PTHREAD_MUTEX_INITIALIZER             {0}
            #define  PTHREAD_RECURSIVE_MUTEX_INITIALIZER   {0x4000}
            #define  PTHREAD_ERRORCHECK_MUTEX_INITIALIZER  {0x8000}
            所謂的動態(tài)初始化
            pthread_mutex_init; pthread_mutex_destroy

            然后就是一些pthread mutex的基本處理函數(shù)了
            lock,unlock
            trylock;

            這個trylock需要好好理解下,嘗試獲取lock,如果OK,那么lock他然后 return 0; 否則也不會suspend住,而是直接返回EBUSY

            pthread_mutex_destroy, 會先去try lock,然后處理掉這個mutex的值。

            這里稍微提一下

            int pthread_mutex_trylock(pthread_mutex_t *mutex)
            {
                
            int mtype, tid, oldv, shared;

                
            if (__unlikely(mutex == NULL))
                    
            return EINVAL;

                mtype  
            = (mutex->value & MUTEX_TYPE_MASK);
                shared 
            = (mutex->value & MUTEX_SHARED_MASK);

                
            /* Handle common case first */
                
            if ( __likely(mtype == MUTEX_TYPE_NORMAL) )
                
            {
                    
            if (__atomic_cmpxchg(shared|0, shared|1&mutex->value) == 0{
                        ANDROID_MEMBAR_FULL();
                        
            return 0;
                    }


                    
            return EBUSY;
                }




            __likely/__unlikely函數(shù)用來告訴編譯器優(yōu)化代碼,類似if else中最有可能or最沒有可能發(fā)生的Case

            mutex就有deadlock的問題,單線程,如果有代碼重入重復獲取鎖就會deadlock,因為你走不到你unlock的地方去;另外
            常見的deadlock就是lock比較多,又沒有設計好順序,這個應該從業(yè)務邏輯上就應該定義好,當然有時候有的人用的時候or改代碼的時候
            并沒有理清這些lock的關系,是否有dependency,比較難通過借用一些編譯系統(tǒng)來Cover住,然后改完就有bug。

            然后還有一種需要設計好的是鎖的粒度
            太粗太細都不好
            粒度太粗,lock住的東西太多,很多線程都要等lock,最后這個東西會演變成一個串行的東西
            粒度太細,lock又變的太多,不停的需要lock/unlock,performance就會變差。
            目前看到的Android上的很多l(xiāng)ock都太粗。

            rw鎖 ->讀寫鎖
            基本理念就是讀不會影響臨界區(qū)發(fā)生變化
            所以讀模式的rw lock可以多個人占用,寫模式的rw lock時能被一個線程lock

            只要有寫模式lock請求,那么后面的讀模式lock請求一般實現(xiàn)是都會被Suspend住,不然因為讀模式下,可以重復lock,如果不
            suspend,那么寫模式的lock請求有可能永遠得不到相應。
            rw鎖一般用在 read比 write行為多的多的場景,允許多線程并發(fā)去讀,單一線程去寫。

            然后會想到spinlock,可以去網(wǎng)上search看下基本概念,spinlock一般在SMP架構(gòu)下會比較有效果。

            mutex是一種同步機制or講這是一種互斥機制 -> Java synchronize
            還一種就是條件變量 condition.. -> wait/notify

            這里有段話很好
            條件變量給多個線程提供了一個回合的場所,條件變量與互斥量一起使用的時候,允許線程以無競爭方式等待特定的條件發(fā)生。

            作業(yè):
            1.線程之間傳遞數(shù)據(jù)不要用stack變量,用放到下面這些地方的變量就好,RW/RO/ZI/Heap就沒事了
            4.
            現(xiàn)在一般都是這樣

                pthread_mutex_lock(&s_startupMutex);

                s_started = 1;
                pthread_cond_broadcast(&s_startupCond);

                pthread_mutex_unlock(&s_startupMutex);

            會在broadcast后才unlock
            否則有比較高的概率,unlock后,被其他線程將條件改掉,這個時候broadcast出去就沒有意義了。
            但是這種也有可能會被另外一個線程,并非wait在那里的線程改變條件值

            所以 pthread_cond_wait 返回并不意味著條件一定為真了
            最好是always check條件
            類似這種
                while (s_started == 0) {
                    pthread_cond_wait(&s_startupCond, &s_startupMutex);
                }

            posted @ 2013-06-03 17:03 Torres 閱讀(200) | 評論 (0)編輯 收藏

            信號
            提供異步時間處理方式
            觸發(fā)時機:
            1.終端命令
            2.硬件異常,由kernel拋向?qū)腜rocess
            3.kill函數(shù)/kill命令(超級用戶or Process的user相同,這里的user id一般是指實際用戶ID or 有效用戶ID,如果支持 _POSIX_SAVED_IDS,那么檢查saved-user-id)
            4.軟件觸發(fā)(滿足信號條件)

            這里也稍微解釋下Kill這個東西,kill函數(shù),kill命令并不是字面上殺掉某些東西,kill只是在特定的時間發(fā)送信號,
            具體的處理取決于信號本身和信號的處理方式。

            信號的處理方式:
            這個有點類似Java Exception的處理方式,catch住or往上throw

            APUE講的信號處理方式有三種
            1.忽略,SIGKILL/SIGSTOP不能忽略;一般如果Process自己不管的話,應該會走到系統(tǒng)默認的處理流程中,所以不能忽略這件事情是系統(tǒng)自己就會保證的。
            2.捕捉信號,例如SIGCHLD,一般parent會call waitpid取子進程終止狀態(tài),避免他處理Zombie狀態(tài)。
            3.執(zhí)行系統(tǒng)默認動作

            我感覺1和3可以歸為一類吧,只是有些signal沒有系統(tǒng)默認動作,然后就跳過去了。一般的系統(tǒng)默認動作也就是終止進程。

            SIGKILL/SIGSTOP不能被忽略的原因我覺得講的不錯,提供超級用戶終止進程的可靠方法,不然后續(xù)有可能進程的行為是未定義的.

            signal函數(shù)定義:(

            void (*signal(int signo, void(*func)(int)))(int);

            這個C函數(shù)的聲明有點難看懂

            APUE講signal的語義與實現(xiàn)有關,所以建議大家都用sigaction

            不過我看了下Android Bionic下的實現(xiàn),分別包含BSD以及SYSV的版本,但也都變成了sigaction,所以一般在C庫中就保證了這一點,也就無所謂了。

             

            static __sighandler_t
            _signal(
            int  signum, __sighandler_t  handler, int  flags)
            {
                
            struct sigaction  sa;
                __sighandler_t    result 
            = SIG_ERR;

                sigemptyset( 
            &sa.sa_mask );

                sa.sa_handler 
            = handler;
                sa.sa_flags   
            = flags;

                
            if ( !sigaction( signum, &sa, &sa ) )
                    result 
            = (__sighandler_t) sa.sa_handler;

                
            return result;
            }



            __sighandler_t bsd_signal(
            int signum, __sighandler_t handler)
            {
              
            return _signal(signum, handler, SA_RESTART);
            }


            __sighandler_t sysv_signal(
            int signum, __sighandler_t handler)
            {
              
            return _signal(signum, handler, SA_RESETHAND);
            }



            exec函數(shù)會講信號處理方式還原為系統(tǒng)默認,這個應該毫無疑問,exec后地址空間就不一樣了,保留之前的捕捉函數(shù)也是無意義的;在這之前是有意義的,我的意思是說fork后exec之前。

            后面有列一大堆是否可以重入的函數(shù),不太想記,然后講到了malloc
            我想不能重入的函數(shù)無外乎兩種類型
            one: 自己有maintain一些全局or static變量 --> malloc 維護分配內(nèi)存時static heap linklist
            two: 函數(shù)參數(shù)里面有引用之類的,會影響調(diào)用者的情況。

            然后我看到這個立馬就想到了線程安全,malloc是線程安全的麼?
            去看了下Bionic的實現(xiàn),然后就又看到下面這個名字 Dong Lea,馬上就會想到Java Concurrent庫,看到這個你直接就會有想法,Bionic里面這個malloc肯定是線程安全的;
            有時候被這些東西搞的很累,比較煩他,而且Bionic里面用的malloc的那個實現(xiàn)版本(aka dlmalloc)我又沒看懂:(,只好去網(wǎng)上search了一下,有個人做過實驗,有些結(jié)論還是可以參考的
            http://www.360doc.com/content/12/0420/23/168576_205320609.shtml

            總的來說,這個東西取決于C庫實現(xiàn),我想Bionic和Glibc都應該一樣會支持兩種不同的版本,然后編譯的時候就可以確定是否有線程相關操作,然后在link的時候link過來不同的版本

            線程安全和信號安全是兩個概念

            如果在線程安全的malloc中,信號處理函數(shù)中發(fā)生重入,那么應該是會發(fā)生dead lock
            如果是非線程安全中,那么應該是所謂的 undefined behavior.

            前面還有一個概率忘記寫
            早期的Unix系統(tǒng),如果系統(tǒng)在執(zhí)行一個低速系統(tǒng)調(diào)用(基本可以總結(jié)為blocking IO:包括IPC,F(xiàn)ile IO,ioctl),那么如果捕捉到信號,那么系統(tǒng)就會中斷這個system call -->EINTR, 這在當時是有理由的,而且理由看起來也合理,但是由于user有時候并不知道某些系統(tǒng)調(diào)用是否是低速系統(tǒng)調(diào)用,BSD引進了自動重啟的功能,linux follow這種規(guī)則

            然后有幾個版本的signal函數(shù)
            signal默認自動重啟
            _signal(signum, handler, SA_RESTART) 這是由于在sigaction中的flag為SA_RESTART,上面sysV的實現(xiàn)我不想多寫了。
            sigaction也就是可選的了。

            但一般我印象中好多 read/write都是會自己判斷返回值以及errno 是否為 EINTR,然后retry,因為上面這種方式依賴系統(tǒng)實現(xiàn),需要將所有的signal都設定為SA_RESTART,那么如果有某個信號發(fā)生的時候,被他INTR的系統(tǒng)調(diào)用才會自動重啟,不知道默認signal在注冊處理行為的時候是不是如此,感覺不太好用。

            alarm函數(shù)
            對于很多像alarm這種函數(shù),在設計時or使用時均應該考慮臨界值的問題
            alarm的唯一性,是by process的,process單例
            所以如果alarm兩次,那么第一次會被覆蓋,怎么處理第一次未完成的情況->返回值會帶回來剩余的時間
            怎么取消設定的alarm
            傳入值為0 means cancel
            總之了,這個東西設計的時候還蠻完善的

            后面還有一堆有關signal的標準函數(shù),這里也就不一一列舉

            作業(yè):
            1.去掉for(;;), 那捕捉到SIGUSR1就返回了,pause()只要捕捉到信號,等信號處理完時自己就會返回并帶回EINTR。不曉得為啥有這種題目...
            2.實現(xiàn)sig2str,這有點無聊了.:(不寫
            3.畫runtime stack的樣子。
            4.IO操作的超時最好不要采用alarm的方式,各種原子問題,select/poll是最好的選擇。
            后面有好一些是要寫代碼的...
            我略想,然后就不太愿意寫了.
            PS:有些東西略難...我也是有點想不清楚的樣子,打算慢慢搞。

            后面決定把標題改一下,順利看完書吧...Orz.

            posted @ 2013-06-02 21:52 Torres 閱讀(255) | 評論 (0)編輯 收藏

            第九章 進程關系
            也是首先記錄基本概念

            關于linux終端的概念,這個最好仔細想一下,因為之后會有偽終端的內(nèi)容,如果不想的具體一些,那么會比較難有感覺。下面有個鏈接,我覺得寫的還可以。
            http://blog.csdn.net/smstong/article/details/8760331

            我寫下我的理解,肯定有不太對的地方,但只能到此。

            廣義的講: 終端就是用來和User交互的設備,鍵盤,顯示器,打印機...
            這里一般講的是狹義的終端:
            一般有兩種: 字符啞終端; 圖形終端
            字符啞終端,就是自己沒有處理能力,只能輸入輸出的;
            圖形終端其實他有處理能力,例如framebuffer...
            linux中,真正能稱得上是終端的其實物理組件就是鍵盤+顯示器,或者稱這兩個東西叫做控制臺
            但是linux虛擬出來了6個終端設備
            tty1 ~ tty6
            而/dev/tty0是一個symbol link指向當前終端,/dev/console則可以為/dev/tty0提供緩沖(這個我不確定)

            tty7是圖形終端
            圖形終端中,會用軟件類似rxvt虛擬出更多的偽終端,類似我們看到的/dev/pts/0...

            偽終端是一種虛擬終端
            但是有時候所謂的虛擬終端還是能對應上物理設備

            對于偽終端pts和ptm
            我自己感覺就像PIPE,只不過輸入輸出時有終端行規(guī)程來控制格式。

            后面有講會話,會話由進程組構(gòu)成,不過我想不清楚運用場景...

            這一章有一些沒看懂,進程組,會話,控制終端...因為不怎么用,先放這里,以后再翻。
            沒有作業(yè)...
            posted @ 2013-05-31 19:50 Torres 閱讀(254) | 評論 (0)編輯 收藏

            記錄一些基本概念
            PID == 0的是Swapper進程(調(diào)度進程),這個Process的RO應該是pre load的時候都放到內(nèi)存里面了,不run任何磁盤上的code,屬于系統(tǒng)進程。
            PID == 1的是init進程。這個是一個以root運行的用戶進程
            fork,我有看到一個詞fork hope...我覺得不錯,我也希望我能fork出hope...
            fork的返回狀態(tài)是有原因的,APUE的解釋還不錯吧
            fork 在 父進程中返回子進程的 PID,因為沒有任何API能知道 child process id,而且child可能會有很多。。。
            fork 在 子進程中返回0,是因為0是swapper進程,所以沒關系;也沒必要返回父進程ID,可以通過getppid()得到。
            fork后,子進程父進程共享正文 RO
            RW ZI Heap Stack是父進程的副本,副本意味著copy
            linux已經(jīng)有很多使用COW了(copy-on-write),這個東西應該是要借助MMU
            因為基本fork后會跟exec,那樣子所有的東西又都需要馬上被替換掉
            都設置為Read Only,無論是parent還是child只要一個試圖修改,那么就copy,單位應該是MMU的Page
            linux有提供clone,這個東西可以由調(diào)用者控制什么東西是要和父進程共享的,我還沒看到pthread的實現(xiàn),猜想應該就是通過這東西實現(xiàn)資源共享的。
            tips:{
            對于字符串求長度,sizeof會計算null,這也正常,因為sizeof()是算memory被分配的大小;strlen計算的是有效字符的長度,不包含null。
            如果字符串是常量,sizeof能在編譯時被優(yōu)化,也變成常量,strlen則是函數(shù)調(diào)用不能被優(yōu)化。
            }
            vfork
            具體實現(xiàn)我沒有具體去想,這個東西也沒有什么特別的,跟fork不一樣的是,他必須接上exec or exit,否則會run在父進程空間而不做任何復制
            所以會有同步,在子進程還未call exec or exit前,父進程會被suspend。要不然父進程受的影響便是未知的。
            另外這里也有討論exit函數(shù)在做清理工作時對流的處理
            應該是說 一般exit不會去做關閉流的動作,而把這件事情留給kernel,只是flush stream就好,否則類似遇到vfork這種會在父進程空間做事情的,
            如果call exit把流給關閉了,會有一些不預期的結(jié)果。
            父進程提前終止,那么子進程的父進程都會被置為1--> init process.
            init process會為每個子進程無論是自己fork出來的還是因為父進程提前終止而領養(yǎng)的做wait獲取終止狀態(tài),避免zombie進程
            處理方式應該是收到SIGCHLD后call wait(null)
            wait系列的函數(shù)還蠻多的...
            對于不想使自己的子進程處于zombie狀態(tài)的,自己有還想做一些事情的且不想關心子進程結(jié)束狀態(tài)的可以fork兩次,exit第一次出來的進程,wait他,然后第二次fork出來的
            進程的父進程就變成init了,不過需要使用這種技巧的Case我沒想到。。。

            關于exec系列函數(shù),我想最好還是記住,因為經(jīng)常會用,不能每次用都去查吧。
            一共6個,exec能給的最多就這三個參數(shù),要run的程序路徑;要給run的程序的參數(shù);要給run的程序的環(huán)境變量
            其中只有execve是 system call,其他都是庫函數(shù)。
            execve(const char* name, char* const argv[], char* const envp[])

            這里又有講三個用戶ID的用法
            實際用戶ID;有效用戶ID;保存的設置用戶ID

            這里有講一個Case,應該算經(jīng)典Case,也只有類似Case才能讓你明白保存的設置用戶ID的用途

            man程序為設置用戶ID/設置組ID,就會在兩種權(quán)限中切換。
            運行man的時候
            real user id == login id
            effective user id == man
            saved effective user id == man

            在man訪問完他自己擁有的那些file后,想運行一些命令call setuid(getuid())因為我們不是root
            real user id == login id
            effective user id == login id
            saved effective user id == man
            這個時候運行命令就是我們login時候的user的權(quán)限

            完了后面想設置回去的時候再call setuid(euid),這里euid就是man,之前可以保存在man自己的Context中
            這個時候euid == saved effective user id,所以effective user id又被設置為man,這種情況下如果沒有saved effective user id那用戶權(quán)限就回不去了。
            real user id == login id
            effective user id == man
            saved effective user id == man

            所以注意setuid 如果是root用戶,那么三個ID都會被設置成目標ID,如果不是
            則effective user id可能會改變 當目標ID為 real user id or saved effective user id的時候,從設計的角度仔細想,這樣子也合理。

            system的實現(xiàn)
            execel shell,而不是直接去fork/execel本身要system的程序,那樣會變得異常復雜,你需要解析cmdString,分離出程序和參數(shù),帶入和shell一樣的環(huán)境變量,找到目的程序,execute他,然后帶回結(jié)果。
            一般會去call system的程序不要使用set-user-id,這樣容易導致安全泄露。容易讓system出來的那個process權(quán)限擴大,不易控制。

            貼一個system的實現(xiàn),非常nice
            #include <sys/types.h>
            #include 
            <signal.h>
            #include 
            <stdlib.h>
            #include 
            <unistd.h>
            #include 
            <paths.h>
            #include 
            <sys/wait.h>

            extern char **environ;

            int
            system(
            const char *command)
            {
              pid_t pid;
                sig_t intsave, quitsave;
                sigset_t mask, omask;
                
            int pstat;
                
            char *argp[] = {"sh""-c", NULL, NULL};

                
            if (!command)        /* just checking */
                    
            return(1);

                argp[
            2= (char *)command;

                sigemptyset(
            &mask);
                sigaddset(
            &mask, SIGCHLD);
                sigprocmask(SIG_BLOCK, 
            &mask, &omask);
                
            switch (pid = vfork()) {
                
            case -1:            /* error */
                    sigprocmask(SIG_SETMASK, 
            &omask, NULL);
                    
            return(-1);
                
            case 0:                /* child */
                    sigprocmask(SIG_SETMASK, 
            &omask, NULL);
                    execve(_PATH_BSHELL, argp, environ);
                _exit(
            127);
              }


                intsave 
            = (sig_t)  bsd_signal(SIGINT, SIG_IGN);
                quitsave 
            = (sig_t) bsd_signal(SIGQUIT, SIG_IGN);
                pid 
            = waitpid(pid, (int *)&pstat, 0);
                sigprocmask(SIG_SETMASK, 
            &omask, NULL);
                (
            void)bsd_signal(SIGINT, intsave);
                (
            void)bsd_signal(SIGQUIT, quitsave);
                
            return (pid == -1 ? -1 : pstat);
            }

            這里使用了vfork和execve,他的實現(xiàn)細節(jié)以及可移植性做的還是蠻完美的。

            作業(yè):
            8.1 如果并非如此那么exit就沒有關閉stdout,這在之前也講過一般的C庫都不會這么多此一舉,一般他們只會去flush stream不會close
            所以如果想達到那種效果,可以自己去顯示去fclose

            8.2
            這個有點復雜啦,vfork后理論上應該立即調(diào)用exec才對,如果不去exec or exit,那么子進程就run在父進程的數(shù)據(jù)空間,如果子進程從另外一個函數(shù)返回,不call exit,那么就會一直等到main函數(shù)退出,父進程才會執(zhí)行,但是這個時候stack里面已經(jīng)沒有任何東西了,我不確定會發(fā)生什么事情

            8.5
            不提供返回保存的有效用戶ID的函數(shù)

            8.6 8.7稍后貼上source code.


            posted @ 2013-05-31 00:25 Torres 閱讀(203) | 評論 (0)編輯 收藏

            第六章不看,直接到第七章

            所謂的啟動例程用C寫起來一般是這樣,但一般情況這個會是匯編來寫
            exit(main(argc, argv))

            exit函數(shù)
            _exit和_Exit立即進入kernel,exit則需要做完必要的清理工作,先run注冊過的終止處理程序,然后清理沒有關閉的流。
            exit _Exit是ISO C標準,_exit則是Posix,所以頭文件不一樣
            前面是stdlib.h 后面是unistd.h

            關于malloc,calloc,realloc
            malloc分配的空間初始值不確定;calloc會幫忙清零;realloc可以動態(tài)分配合適的大小空間,如果不夠會有copy原始數(shù)據(jù)的動作,所以對realloc之前的memory記錄指針不是好方法,因為realloc完就可能失效。

            這三個都是去call sbrk來擴大縮小heap
            但一般只是擴大,在free的時候 malloc 會把擴大的 memory cache起來(virual memory)

            setjmp, longjmp
            跳轉(zhuǎn)函數(shù),支持跨越函數(shù)幀的跳轉(zhuǎn),goto是函數(shù)內(nèi)跳轉(zhuǎn)
            注意: longjmp到setjmp的點后,一般情況 全局/局部/局部靜態(tài)變量都沒辦法恢復到setjmp時的值;register/volatile變量在編譯后(cc -O)會放到寄存器中,這些都能實現(xiàn)回滾。這也就說明setjmp的時候,jmp_buf并沒有保存這些值。

            7.1 如果不調(diào)用return 和 exit 函數(shù)返回值用了printf的返回值,Orz...我沒想清楚怎么做的。

            7.2 取決于printf面對的設備類型,行緩沖的話,碰到換行就會被輸出,如果是全緩沖,那么則要么需要call fflush,要么等stream被close。

            7.3 不用參數(shù)傳遞,不用全局變量,Orz...要傳遞argc, argv -->NO

            7.4 通常null就是0,一般指針為 null 的時候意味著空指針引用,一般0都不會去放數(shù)據(jù),RO的起始位置在一個更高的地址,0會是無效的,用來指引程序出錯。

            7.5
            typedef void func(void);
            int atexit(func* fp);
            或者
            typedef void (*func)(void);
            int atexit(func fp);
            我喜歡下面那種,因為函數(shù)本身就是個pointer。

            7.6 calloc會將分配的空間清零,ISO C不保證0值和空指針相等

            7.7 size輸出的是靜態(tài)的RO,RW,ZI,像Heap,Stack是runtime才有的。

            7.8 理論上不帶debug信息的.out文件應該大小都是等于RO+RW+ZI的。

            7.9 這就是.a和.so的區(qū)別了,link的時候靜態(tài)庫會被直接copy過來,所以會變大很多。

            7.10 變量作用域的故事。
            posted @ 2013-05-28 20:00 Torres 閱讀(186) | 評論 (0)編輯 收藏

                 摘要: 這一章會讓你想起一些塵封的記憶上大學的時候,教C語言的老師會教大家文件IO,那個時候講的都是標準輸入輸出,都是C庫的實現(xiàn),和第三章Unbuffered IO要區(qū)別開來,目的之前講過減少System Call的調(diào)用次數(shù),提高PerformanceJava上也有類似的實現(xiàn),只不過Java的實現(xiàn)會更加Common一些,類似BufferedInputStream/BufferedOutputStream,...  閱讀全文
            posted @ 2013-05-28 19:24 Torres 閱讀(299) | 評論 (0)編輯 收藏

            這里要稍微寫一些linux下復雜的權(quán)限管理,應該只是一小部分知識,還有一些關于cap的東西以后再看。

            與process關聯(lián)的ID
            誰execute這個process的人稱為real user id,應該就是登陸時使用的user
            real group id也一樣.
            一般在一個登陸Session中,這兩個值都不會改變,但是超級用戶進程可以改變
            像Android中每個APK有獨自的User id,然后類似rild之類的native deamon process就會嘗試改變自己的user id為類似radio之類的東西。

            在運行中檢查權(quán)限所使用的id稱為有效用戶id,effective user id
            然后還有有效組ID
            附加組ID

            一般有效用戶ID == 真實用戶ID, 有效組ID == 真實組ID
            但是如果在可執(zhí)行文件的st_mode中有設置 set-user-ID/set-group-ID
            那在執(zhí)行這個文件的時候 有效用戶ID和有效組ID會變成文件的owner

            一般有設置set-user-ID/set-group-ID的程序都會獲得額外的權(quán)限

            關于文件權(quán)限,有一些容易引起誤解的,也還有一些我自己也沒有理解清楚的

            文件權(quán)限比較好理解,O_TRUNC需要文件具有寫權(quán)限。

            文件夾的寫權(quán)限,應該就是類似能不能在其中create/update/delete文件和文件夾
            文件夾的讀權(quán)限,應該就是讀文件夾里面的文件/文件夾列表,通常我們 ls 當前文件夾就必須具有讀權(quán)限
            文件夾的執(zhí)行權(quán)限,這個東西聽說又叫搜索位,通常我們cd XXX,就必須在當前文件夾下搜索XXX是否存在,然后就是當我們訪問類似這種很長路徑的文件/aaa/bbb/ccc/dd.txt
            對aaa,bbb,ccc必須具有執(zhí)行權(quán)限,就是搜索

            一般如果具有讀權(quán)限就應該就可以搜索,如果這之前有區(qū)別,就是應該是搜索的范圍大于能讀到的內(nèi)容。
            也就是之后需要知道一個Folder本身里面有寫什么內(nèi)容,我目前知道的是一定有當前的文件列表--> TODO: 看情景分析 or 去看下 ls 的實現(xiàn),ls里面有比較多參數(shù)有些是需要x的有些是需要r的,例如 ls -l如果沒有執(zhí)行權(quán)限就只能拿到 name ,能知道是folder還是file,除此之外其他的東西都拿不到,文件本身的權(quán)限,user,group

            -->linux目錄中記錄的應該是只有兩個東西 inode & name.這與文件系統(tǒng)的實現(xiàn)有關。

            另外增刪文件都需要WX權(quán)限

            另外內(nèi)核對文件操作權(quán)限的判斷順序...如果是owner就會看owner權(quán)限,group權(quán)限就不會看了,如果在group中則others也就不看了,這個邏輯上正常,而且可以想一下kernel的實現(xiàn),一定是if-else的判斷出結(jié)果后直接返回。


            這里就要提如果新建文件/文件夾的時候文件夾的owner user id和group id是誰
            user id會是process 有效用戶ID
            group id可以有兩種選擇父folder的group id;or process有效組ID
            linux中根據(jù)文件系統(tǒng)的不同有的可以在mount fs的時候選擇

            文件長度: st_size
            lseek到文件末尾之后的空間,會照成文件空洞
            文件長度會+空洞的大小,但是這些空洞未必會占用磁盤空間。
            du 命令可以看磁盤空間大小

            符號鏈接和所謂的硬鏈接完全是兩回事,建議細讀4.14,不要去上網(wǎng)search類似我這種自己記錄給自己看的blog...:(

            這章內(nèi)容比較多而且雜,主要是stat中的每個參數(shù)的意義,需要思考想清楚的東西也比較多,有比較多關于文件操作的System Call

            作業(yè)開始:
            之后只寫有點意義的題目,無意義的也沒什么可以寫的pass
            4.1 stat和lstat的區(qū)別,stat基本不會關心是否是S_IFLNK(符號鏈接),應該是看到S_IFLNK會往下去找真正的File,然后拿到屬性值
            而lstat旁道S_IFLNK則會直接返回。第一次看到這樣的函數(shù)設計怪怪的,我的初始感覺是反的。我覺得stat是一個common的設計,不會針對不同的File類型來做一些區(qū)別。Orz...最后不是。

            4.2 umask 777意味著rwxrwxrwx全部變沒掉,但是這也沒關系,不知道出題人的意思

            4.5 目錄和符號鏈接的長度不可能為0,目錄創(chuàng)建出來就會包含. 和 ..而且 . 的inode指向自己本身會占磁盤空間,符號鏈接肯定也是不能為0的,其中有存指向的鏈接path

            4.6 這個有點意思,后面貼代碼

            4.7 這個看了下答案,沒看懂>為什么kernel默認的創(chuàng)建文件賦予的權(quán)限“可能會,也可能不會”受unmask的值的影響?

            4.8 du df的區(qū)別...

            4.9 ~ 后面的題大概看了下,不想寫了,好多細節(jié)...

             


             

            posted @ 2013-05-27 16:42 Torres 閱讀(258) | 評論 (0)編輯 收藏

            看了下第一次作業(yè),那個排版和高亮背景有點瞎,這次能好點就不錯了.

            第二章是關于POSIX以及XSI的一些標準和limitation,就不做作業(yè)了,直接到第三章,沒有睡午覺,有些困,不要寫錯...

            先貼一些比較好的思路
            (1)
            在AF_LOCAL(Posix: AF_UNIX)的Socket編程中,對Client端來說需要connect
            int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
            這里需要給serv_addr一個length,這個length是一個真實的地址長度。
            當socket為AF_UNIX的Socket時 socket address為sockaddr_un里面存放著地址類型以及socket path,我們需要知道sockaddr_un中真正存放數(shù)據(jù)的長度。

            函數(shù)offsetof是不錯的選擇
            這個function的實現(xiàn)看起來可以學習,我先抄一段貼下面

            #define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )

            宏功能:獲得一個結(jié)構(gòu)體變量成員在此結(jié)構(gòu)體中的偏移量。

            1. ( (TYPE *)0 ) 將零轉(zhuǎn)型為TYPE類型指針;
            2. ((TYPE *)0)->MEMBER 訪問結(jié)構(gòu)中的數(shù)據(jù)成員;

            3. &( ( (TYPE *)0 )->MEMBER )取出數(shù)據(jù)成員的地址,即相對于0的偏移量,要的就這個;
            4.(size_t)(&(((TYPE*)0)->MEMBER))結(jié)果轉(zhuǎn)換類型,size_t應該最終為unsigned int類型。
            此宏的巧妙之處在于將 0 轉(zhuǎn)換成(TYPE*),這樣結(jié)構(gòu)體中成員的地址即為在此結(jié)構(gòu)體中的偏移量。

            這里主要還是利用了 '->' 這個符號的功能,編譯器解析這個符號的時候應該就是通過偏移量取值,所以這里先定義好取值規(guī)則(TYPE*)然后利用偏移量取值反向再取地址,再然后因為起始地址是0,取回的地址就變成了真正的偏移量。
            還有一個知道m(xù)ember求整體的Ptr的Marco應該也蠻好用,括號比較多,我沒頭去看具體實現(xiàn)了...

             

            #define container_of(ptr, type, member) ({ \
            const typeof( ((type *)0)->member ) *__mptr = (ptr); \
            (type *)( (char *)__mptr – offsetof(type,member) );})

            宏功能:從結(jié)構(gòu)體(type)某成員變量(member)指針(ptr)來求出該結(jié)構(gòu)體(type)的首指針。

            (2)
            有討論一些東西,沒有搞清楚,貼在這里以后有心力了再看,在看Rild,然后pthread在做線程同步的時候需要先lock,也許大家都是這樣做,也一直這樣做,但是突然就想到之前Java的時候討論過的東西為什么wait/notify 一定要在一個synchronize block中,當然你可以講,如果不會有exception拋出,你也還可以講wait本來就會先釋放lock然后把當前的thread放到Object waiting thread pool中然后掛起當前線程,實現(xiàn)就是這樣子,但是從設計來講,原因我沒有想的特別清楚,如果不去lock,是不是因為wait/notify本身有很多操作沒辦法做multi thread...

            然后就是關于select的一些輔助的Marco,類似FD_SET,F(xiàn)D_ZERO之類的,簡單講就是一個long數(shù)組,每個元素的每個bit代表一個fd,然后做set,clear之類的動作,最大不能超過那個1024

            今天先到這里,寫個序,明天正文...Orz

            (3)
            繼續(xù)討論東西
            有人為了在C++中作出類似Java可以自動釋放內(nèi)存的東西,也就是所謂的智能指針,Android有幫忙實現(xiàn)strong pointer/weak pointer,他的大概意思就是用一個第三者來看住當前new出來的object,采用的方式一般是引用計數(shù),這種方式也許在Design Pattern中有專門的名字。一般利用編譯器對這個第三者做自動釋放的時候去檢查計數(shù)器,看是否能釋放他看管的object,C++里面編譯器會自動去call ~Constructor就例如局部變量。

            有一些復雜的Case,就算是交叉引用了,Strong Pointer/Weak Pointer就是為了解決交叉引用的問題,從語義上講父Object引用子Object,用SP,反過來用WP
            只要SP為0,就可以釋放Memory

            今天大家有講Java的WeakReference,SoftReference,我就含糊了,我以為這東西和上面一樣。
            Java的WeakReference,SoftReference跟上面講的完全不一樣,由于沒分清楚,混淆了。JVM就會處理引用交叉的問題,JVM處理引用交叉可以使用類似有向圖,如果呈環(huán)狀而且環(huán)中的任意點都沒辦法到GC Roots(VM stack中的Reference/static reference/JNI reference),那么就都GC掉應該也沒問題。
            所以Java中的所謂的WeakReference/SoftReference跟SP/WP不一樣,Java中普通賦值的reference都是強可及的Strong Reference。
            WeakReference只是用來關心下那些將要被回收卻沒有回收的Object,一旦被GC Thread掃到就會被釋放。
            而SoftReference是JVM對Object的Cache,只要內(nèi)存足夠不會被釋放。

            貼一個講Android SP/WP的鏈接,之前有印象看過。
            http://blog.csdn.net/luoshengyang/article/details/6786239


            (4)
            Keep Thinking...
            某人讓我寫一個處理字符串的函數(shù),30min, 我怎么也不能讓他失望的,是吧~無論11點多的時候我頭有多暈,我很開心的在那里寫,很直白的講,我自己用的本是個游戲機,沒啥好環(huán)境。用C寫了個,然后跑出來的結(jié)果出乎預料,我想可能是長時間指手畫腳慣了~仔細想了下一些文件操作

            然后仔細看了一下二進制文件和文本文件,這也是糾正之前的一些疑惑。
            基本概念是:
            我自己感覺文本文件和二進制文件沒啥區(qū)別,文本文件就是有明確普通解碼方式的顯示字符的文件,可以理解說文本文件也是一種二進制文件,只不過這個二進制文件的解碼方式是固定好的(ASCII/UTF-8之類)

            然后就是讀寫文本文件時在Windows上有一些轉(zhuǎn)換,寫的時候會在\n前面自動加上\r,讀的時候反過來,Linux則沒有區(qū)別。

            有一件事情是要明確知道的
            像printf之類的東西,幫助我們輸出的,其實我們往printf里面塞的是 int 值,這是一個二進制的值,但是我們從stdout中看到的則已經(jīng)變成了字符,一定有人幫忙做了轉(zhuǎn)換.
            我原本以為printf會是檢查目標文件,如果目標文件為二進制則直接輸出原本的值,如果目標為文本文件就輸出對應值的字符

            這種理解是錯的,不同的輸入輸出函數(shù)帶來了不同的結(jié)果
            類似printf這種格式話輸出函數(shù),會把輸出內(nèi)容按照字符以默認的編碼方式encode放入文件,所以那個文件里面寫的值已經(jīng)是真實值的一種顯示方式(字符)了
            然后scanf這種函數(shù)在讀的之后,也會把字符轉(zhuǎn)換為對應的值,一般ASCII就直接 -0x30了

            像read/write這種function他們不會做任何轉(zhuǎn)換,直接就把值放進去了,所以你用文本文件打開的時候,按照默認的decode方式來解析他的時候會發(fā)現(xiàn)是亂碼。

            這個東西與文本文件二進制文件沒有關系。
            不知道是不是我自己很弱,現(xiàn)在才知道這個東西。

            作業(yè)終于開始:
            3.1 這章主要講文件Unbuffered IO,這里的unbuffered是指userspace沒有buffer來緩沖,kernel一定還是有緩沖的,一般userspace的緩沖做在標準C lib中
            另外像Android這種架構(gòu)Java layer會做緩沖(BufferedInputStream/BufferedOutputStream, 默認是8K)
            Java layer的緩沖可以減少 JNI 調(diào)用次數(shù)
            C lib緩沖可以減少 system call 的次數(shù)

            3.2 自己實現(xiàn)dup2
            不能用fcntl那就只能用dup了
            這里是想要讓做題的人知道,kernel對進程文件描述符的分配原則是 "取最小的沒有使用的"
            所以用dup實現(xiàn)Mydup(fd1, fd2)
            1.fd2 < fd1: close fd2; dup(fd1)得到的值就是fd2
            2.fd2 = fd1: 直接return fd1
            3.fd2 > fd1: 那就只能無限dup了,直到返回值為fd2,然后close掉 fd1 ~ fd2-1
            這里需要對文件描述符表,文件表,inode之類的概念熟悉。

            3.3
            這里主要是要說文件表項的分配,每次open kernel都會分配新的文件表項,dup的作用就是拿到另外一個文件描述符指向相同的文件表項,但是每支文件只有同一份
            inode。
            fd1,fd2會指向同一份文件表項
            fd1,fd2,fd3的文件表項會指向同一份inode
            fcntl 作用于fd1,F(xiàn)_SETFD用來設定 close_on_exec 標志這個東西是在文件描述符中的,所以只會影響fd1
            如果是F_SETFL則會影響fd1, fd2文件狀態(tài)標志都放在文件表中。

            3.4
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            if (fd > 2) close(fd);
            這個東西的作用應該就是重定向標準輸入輸出和錯誤輸出到fd.

            3.5
            沒什么重要的,shell命令一定是從左往右解析執(zhí)行的

            3.6
            雖然是append方式打開,這個東西是針對寫,lseek可以自己選定讀取位置。
            write函數(shù)是否是會每次先lseek到文件尾部?然后再寫,這樣之前l(fā)seek的設置就無效了
            小東西驗證。

            后面貼下代碼
            append會影響每次write,只要是O_APPEND打開,每次write都會添加到文件末尾。

            Done

            posted @ 2013-05-22 22:58 Torres 閱讀(155) | 評論 (0)編輯 收藏

            記性不好,所以作業(yè)做起來--從第一章開始,期望有始有終
            1.1 對根目錄(/)來說,沒有parent,所以'.' or '..'就沒有區(qū)別了,可以用ls -li來看inode,/.. 代表他本身
            1.2 多實例,Process ID遞增,分配Process ID的具體方式-->//TODO:去查情景分析
            1.3 const char* 放到perror中不想讓perror自己改,其實我覺得strerror也可以const int
            1.4 errno related
            比較早之前errno是by process的,這并不合適,Linux支持多線程存取errno
            extern int* __errno_location(void)
            #define errno (* __errno_location())
            Macro為一個函數(shù),使得__errno_location有機會返回一個TSL的variable
            這里APUE有Wrap一些簡單的打error log的函數(shù),由于errno是一個by thread variable,所以必須先保存
            要不然后面print之類的system call同樣有可能出現(xiàn)error然后覆蓋掉errno
            有看到變參函數(shù),糾正了我自己長期的一個誤區(qū),之前一直以為變參函數(shù)只能用在特定的Case下才有用,例如scanf/printf之類的輸入輸出函數(shù)
            因為他們都有帶類似format的參數(shù),能夠通過%d,%c之類的東西找到后面的變參的個數(shù)以及類型,今天有去看一下va_list/va_arg/va_end的實現(xiàn),會發(fā)現(xiàn)變參函數(shù)是,本來也應該是一種更加common的設計
            這里順帶提一下function call的大體流程,這里需要比較多的背景知識,類似函數(shù)調(diào)用約定,程序內(nèi)存分布...
            找來一張圖比較好看Linux process userspace的布局,印象中windows也一樣只不過windows默認是2G/2G,linux是大家都知道的1G/3G


            userspace從0~0xC0000000,kernel space(0xC0000000~0xffffffff)的樣子跟這個差別比較大,那邊分vmalloc/kmalloc還有類似high memory的概念,這里主要貼出userspace,可以看一下RO,RW,ZI,Heap的位置,順便提一下整個linux virtual memory其實都可以分為兩部分: anonymous memory/mapped memory,像RO就是mapped memory,RW開始是mapped memory,只要有人改變初始值,那就會變成anonymous memory,ZI,Heap,Stack這些都是anonymous memory,與函數(shù)調(diào)用相關的主要是stack -->stack的地址是向低地址增長的,棧底在高地址,棧頂在低地址
            然后介紹兩種常用的函數(shù)調(diào)用約定,一種是C語言默認的函數(shù)調(diào)用約定 __cdecl; 另外一種是PASCAL默認的調(diào)用約定 __stdcall
            這兩種都是從右到左將參數(shù)壓棧,然后再push ebp; mov ebp, esp; 再然后壓入函數(shù)局部變量,不一樣的是cdecl是由caller function將函數(shù)參數(shù)出棧,stdcall是由callee function將函數(shù)出棧。
            這兩種方式各有好處,慨括講一下cdecl的話每個調(diào)用者都必須去做pop stack的動作,code size會變大;stdcall則在callee function本身來看會比較單純
            但是這種類型比較難從編譯器的角度來支持變參函數(shù)。
            變參函數(shù)都是cdecl
            可以玩一下這樣的函數(shù)...

             

             1#include <cstdlib>
             2#include <iostream>
             3
             4using namespace std;
             5
             6void foo(int cnt, )
             7{
             8    cout << "start foo" << endl; 
             9    int* p = &cnt;
            10    for(int i = 0; i < cnt; i++){        
            11        cout << *(++p) << endl;
            12    }

            13    cout << "end foo" << endl;
            14}

            15
            16int main(int argc, char *argv[])
            17{
            18    int a = 1, b = 3, c = 5, d = 7;
            19    void (*functest)(int cnt, );
            20    functest = foo;
            21    functest(4, a, b, c, d);
            22    system("PAUSE");
            23    return EXIT_SUCCESS;
            24}


            加一點對va_list/va_arg/va_end的說法,其實他的實現(xiàn)蠻靈活的

            ///stdarg.h
            #define va_start _crt_va_start
            #define va_arg _crt_va_arg
            #define va_end _crt_va_end  

            ///vadefs.h
            #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
            typedef 
            char *  va_list;
            #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
            #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
            #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
            #define _crt_va_end(ap)      ( ap = (va_list)0 )

            貼一段網(wǎng)上看到的應該是i386的實現(xiàn),類似這種變參函數(shù)會直接去用指針操作stack來找參數(shù)的,因為涉及到對其的問題一定是by platform的,不太好移植
            像_INTSIZEOF(n) 就是int對其
            大概講下意思 va_list 就是 char*,是一個用來找參數(shù)ptr的游標
            va_start(ap, v)  通過第一個固定參數(shù)v 初始化 ap
            va_arg(ap, t)  這里就給Type t了,所以并不一定需要類似printf的 format 來找,caller和callee可以有一些其他的約定方式,這個返回當前類型為t的參數(shù)的值,并且將ap指向下個參數(shù),因為之前ap被初始化的時候其實他并不知道他指向的參數(shù)的類型。
            va_end(ap) 清掉ap
            多說一句,printf系列的函數(shù)還蠻多的,fprintf/sprintf/vprintf/svnprintf...帶f的是面向FILE streaming的,s是針對char buffer的,v則是說參數(shù)是帶va_list的,n則是具體指size
            1.5/1.6討論32位表示時間溢出的,不想寫答案,時間比較晚,第一章作業(yè)寫完。

             

            記錄一些關于系統(tǒng)總線與CPU“位數(shù)"的基本概念

            通常所說的CPU的位數(shù),32位or64位CPU,指的是ALU(算術邏輯單元)的寬度,也就是這個ALU處理數(shù)據(jù)的基本單元的寬度
            所以數(shù)據(jù)總線基本會和ALU寬度相同(有例外,這個我沒想清楚工作原理) -->應該是可以新加一些Module來做轉(zhuǎn)換。
            而地址總線則是CPU尋址的能力,一個是怎么去尋址,一個是尋到地址后,地址中內(nèi)容的寬度(當然這個寬度跟地址類型(byte,short,int)有關,但送給CPU的時候一般是單位次數(shù)送數(shù)據(jù)總線的寬度的數(shù)據(jù)),地址總線決定CPU能訪問的Memory的范圍。

            8086是16位ALU 20位數(shù)據(jù)總線尋址1M
            每次CPU送出的地址都是16位,然后加上段寄存器作為最高4位

             

            posted @ 2013-05-21 23:25 Torres 閱讀(206) | 評論 (0)編輯 收藏

            僅列出標題
            共7頁: 1 2 3 4 5 6 7 
            99久久免费只有精品国产| 亚洲人AV永久一区二区三区久久| 亚洲?V乱码久久精品蜜桃 | 欧美va久久久噜噜噜久久| 久久强奷乱码老熟女| 99久久www免费人成精品| 99久久无色码中文字幕| 热re99久久6国产精品免费| 国内精品久久久久久久久电影网| 热RE99久久精品国产66热| 精品久久久久久国产牛牛app| 日本福利片国产午夜久久| 97热久久免费频精品99| 69国产成人综合久久精品| 高清免费久久午夜精品| .精品久久久麻豆国产精品| 国产一级做a爰片久久毛片| 国内精品久久久久久野外| 久久91综合国产91久久精品| 777米奇久久最新地址| 久久青草国产精品一区| 成人a毛片久久免费播放| 久久久久久A亚洲欧洲AV冫| 久久久中文字幕日本| 一本大道久久东京热无码AV| 国色天香久久久久久久小说| 久久久久久国产精品免费无码| 久久99国内精品自在现线| 高清免费久久午夜精品| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 91亚洲国产成人久久精品| 国产精品成人久久久久三级午夜电影| 久久99精品久久久久久不卡| 欧美成人免费观看久久| 青草国产精品久久久久久| 精品99久久aaa一级毛片| 久久天天躁夜夜躁狠狠| 国产日产久久高清欧美一区| 免费一级做a爰片久久毛片潮| 一本色道久久99一综合| 国产高清国内精品福利99久久|