• <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>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

            轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html, 作者:鄭彥興

            一、信號及信號來源

            信號本質

            信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。

            信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展后,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。

            信號來源

            信號事件的發生有兩個來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。


            二、信號的種類

            可以從兩個不同的分類角度對信號進行分類:(1)可靠性方面:可靠信號與不可靠信號;(2)與時間的關系上:實時信號與非實時信號。在《Linux環境進程間通信(一):管道及有名管道》的附1中列出了系統所支持的所有信號。

            1、可靠信號與不可靠信號

            "不可靠信號"

            Linux信號機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信號機制比較簡單和原始,后來在實踐中暴露出一些問題, 因此,把那些建立在早期機制上的信號叫做"不可靠信號",信號值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是:

            • 進程每次處理信號后,就將對信號的響應設置為默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號處理函數結尾再一次調用signal(),重新安裝該信號。
            • 信號可能丟失,后面將對此詳細闡述。 
              因此,早期unix下的不可靠信號主要指的是進程可能對信號做出錯誤的反應以及信號可能丟失。

            Linux支持不可靠信號,但是對不可靠信號機制做了改進:在調用完信號處理函數后,不必重新調用該信號的安裝函數(信號安裝函數是在可靠機制上的實現)。因此,Linux下的不可靠信號問題主要指的是信號可能丟失。

            "可靠信號"

            隨著時間的發展,實踐證明了有必要對信號的原始機制加以改進和擴充。所以,后來出現的各種Unix版本分別在這方面進行了研究,力圖實 現"可靠信號"。由于原來定義的信號已有許多應用,不好再做改動,最終只好又新增加了一些信號,并在一開始就把它們定義為可靠信號,這些信號支持排隊,不 會丟失。同時,信號的發送和安裝也出現了新版本:信號發送函數sigqueue()及信號安裝函數sigaction()。POSIX.4對可靠信號機制 做了標準化。但是,POSIX只對可靠信號機制應具有的功能以及信號機制的對外接口做了標準化,對信號機制的實現沒有作具體的規定。

            信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的 信號安裝函數sigation()以及信號發送函數sigqueue()的同時,仍然支持早期的signal()信號安裝函數,支持信號發送函數 kill()。

            注:不要有這樣的誤解:由sigqueue()發送、sigaction安裝的信號就是可靠的。事實上,可靠信號是指后來添加的新信號 (信號值位于SIGRTMIN及SIGRTMAX之間);不可靠信號是信號值小于SIGRTMIN的信號。信號的可靠與不可靠只與信號值有關,與信號的發 送及安裝函數無關。目前linux中的signal()是通過sigation()函數實現的,因此,即使通過signal()安裝的信號,在信號處理函 數的結尾也不必再調用一次信號安裝函數。同時,由signal()安裝的實時信號支持排隊,同樣不會丟失。

            對于目前linux的兩個信號安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成 可靠信號(都不支持排隊,仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊。這兩個函數的最大區別在于,經過 sigaction安裝的信號都能傳遞信息給信號處理函數(對所有信號這一點都成立),而經過signal安裝的信號卻不能向信號處理函數傳遞信息。對于 信號發送函數來說也是一樣的。

            2、實時信號與非實時信號

            早期Unix系統只定義了32種信號,Ret hat7.2支持64種信號,編號0-63(SIGRTMIN=31,SIGRTMAX=63),將來可能進一步增加,這需要得到內核的支持。前32種信 號已經有了預定義值,每個信號有了確定的用途及含義,并且每種信號都有各自的缺省動作。如按鍵盤的CTRL ^C時,會產生SIGINT信號,對該信號的默認反應就是進程終止。后32個信號表示實時信號,等同于前面闡述的可靠信號。這保證了發送的多個實時信號都 被接收。實時信號是POSIX標準的一部分,可用于應用進程。

            非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。


            三、進程對信號的響應

            進程可以通過三種方式來響應一個信號:(1)忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及 SIGSTOP;(2)捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;(3)執行缺省操作,Linux對每種信號都規定了默認操作,詳 細情況請參考[2]以及其它資料。注意,進程對實時信號的缺省反應是進程終止。

            Linux究竟采用上述三種方式的哪一個來響應信號,取決于傳遞給相應API函數的參數。


            四、信號的發送

            發送信號的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

            1、kill() 
            #include <sys/types.h> 
            #include <signal.h> 
            int kill(pid_t pid,int signo) 

            參數pid的值信號的接收進程
            pid>0進程ID為pid的進程
            pid=0同一個進程組的進程
            pid<0 pid!=-1進程組ID為 -pid的所有進程
            pid=-1除發送進程自身外,所有進程ID大于1的進程

            Sinno是信號值,當為0時(即空信號),實際不發送任何信號,但照常進行錯誤檢查,因此,可用于檢查目標進程是否存在,以及當前進 程是否具有向目標發送信號的權限(root權限的進程可以向任何進程發送信號,非root權限的進程只能向屬于同一個session或者同一個用戶的進程 發送信號)。

            Kill()最常用于pid>0時的信號發送,調用成功返回 0; 否則,返回 -1。 注:對于pid<0時的情況,對于哪些進程將接受信號,各種版本說法不一,其實很簡單,參閱內核源碼kernal/signal.c即可,上表中的規則是參考red hat 7.2。

            2、raise() 

            1. #include <signal.h>
            2. int raise(int signo)

            向進程本身發送信號,參數為即將發送的信號值。調用成功返回 0;否則,返回 -1。

            3、sigqueue() 

            1. #include <sys/types.h>
            2. #include <signal.h>
            3. int sigqueue(pid_t pid, int sig, const union sigval val)


            調用成功返回 0;否則,返回 -1。

            sigqueue()是比較新的發送信號系統調用,主要是針對實時信號提出的(當然也支持前32種),支持信號帶有參數,與函數sigaction()配合使用。

            sigqueue的第一個參數是指定接收信號的進程ID,第二個參數確定即將發送的信號,第三個參數是一個聯合數據結構union sigval,指定了信號傳遞的參數,即通常所說的4字節值。


            1. typedef union sigval {
            2.          int sival_int;
            3.          void *sival_ptr;
            4.      }sigval_t;

            sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個進程發送信號,而不能發送信號給一個進程 組。如果signo=0,將會執行錯誤檢查,但實際上不發送任何信號,0值信號可用于檢查pid的有效性以及當前進程是否有權限向目標進程發送信號。

            在調用sigqueue時,sigval_t指定的信息會拷貝到3參數信號處理函數(3參數信號處理函數指的是信號處理函數由 sigaction安裝,并設定了sa_sigaction指針,稍后將闡述)的siginfo_t結構中,這樣信號處理函數就可以處理這些信息了。由于 sigqueue系統調用支持發送帶參數信號,所以比kill()系統調用的功能要靈活和強大得多。

            注:sigqueue()發送非實時信號時,第三個參數包含的信息仍然能夠傳遞給信號處理函數; sigqueue()發送非實時信號時,仍然不支持排隊,即在信號處理函數執行過程中到來的所有相同信號,都被合并為一個信號。

            4、alarm() 

            1. #include <unistd.h>
            2. unsigned int alarm(unsigned int seconds)


            專門為SIGALRM信號而設,在指定的時間seconds秒后,將向進程本身發送SIGALRM信號,又稱為鬧鐘時間。進程調用alarm后,任何以前的alarm()調用都將無效。如果參數seconds為零,那么進程內將不再包含任何鬧鐘時間。 
            返回值,如果調用alarm()前,進程中已經設置了鬧鐘時間,則返回上一個鬧鐘時間的剩余時間,否則返回0。

            5、setitimer() 

            1. #include <sys/time.h>
            2. int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));


            setitimer()比alarm功能強大,支持3種類型的定時器:

            • ITIMER_REAL: 設定絕對時間;經過指定的時間后,內核將發送SIGALRM信號給本進程;
            • ITIMER_VIRTUAL 設定程序執行時間;經過指定的時間后,內核將發送SIGVTALRM信號給本進程;
            • ITIMER_PROF 設定進程執行以及內核因本進程而消耗的時間和,經過指定的時間后,內核將發送ITIMER_VIRTUAL信號給本進程;

            Setitimer()第一個參數which指定定時器類型(上面三種之一);第二個參數是結構itimerval的一個實例,結構itimerval形式見附錄1。第三個參數可不做處理。

            Setitimer()調用成功返回0,否則返回-1。

            6、abort() 

            1. #include <stdlib.h>
            2. void abort(void);

            向進程發送SIGABORT信號,默認情況下進程會異常退出,當然可定義自己的信號處理函數。即使SIGABORT被進程設置為阻塞信號,調用abort()后,SIGABORT仍然能被進程接收。該函數無返回值。


            五、信號的安裝(設置信號關聯動作)

            如果進程要處理某一信號,那么就要在進程中安裝該信號。安裝信號主要用來確定信號值及進程針對該信號值的動作之間的映射關系,即進程將要處理哪個信號;該信號被傳遞給進程時,將執行何種操作。

            linux主要有兩個函數實現信號的安裝:signal()、sigaction()。其中signal()在可靠信號系統調用的基礎 上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用于前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實 現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優于signal()主要體現在支持信號帶有參數。

            1、signal() 

            1. #include <signal.h>
            2. void (*signal(int signum, void (*handler))(int)))(int);


            如果該函數原型不容易理解的話,可以參考下面的分解方式來理解: 

            1. typedef void (*sighandler_t)(int)
            2. sighandler_t signal(int signum, sighandler_t handler));


            第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,可以忽略該信號(參數設為SIG_IGN);可以采用系統默認方式處理信號(參數設為SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)。 
            如果signal()調用成功,返回最后一次為安裝信號signum而調用signal()時的handler值;失敗則返回SIG_ERR。

            2、sigaction() 

            1. #include <signal.h>
            2. int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

            sigaction函數用于改變進程接收到特定信號后的行為。該函數的第一個參數為信號的值,可以為除SIGKILL及SIGSTOP 外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構 sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存原來對相應信號 的處理,可指定oldact為NULL。如果把第二、第三個參數都設為NULL,那么該函數可用于檢查信號的有效性。

            第二個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽掉哪些函數等等。

            sigaction結構定義如下:


            1. struct sigaction {
            2.           union{
            3.             __sighandler_t _sa_handler;
            4.             void (*_sa_sigaction)(int,struct siginfo *, void *)
            5.             }_u
            6.                      sigset_t sa_mask;
            7.                     unsigned long sa_flags; 
            8.                   void (*sa_restorer)(void)
            9.                   }

            其中,sa_restorer,已過時,POSIX不支持它,不應再被使用。

            1、聯合數據結構中的兩個元素_sa_handler以及*_sa_sigaction指定信號關聯函數,即用戶指定的信號處理函數。除了可以是用戶自定義的處理函數外,還可以為SIG_DFL(采用缺省的處理方式),也可以為SIG_IGN(忽略信號)。

            2、由_sa_handler指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息;由 _sa_sigaction是指定的信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),它指定一個3參數信號處理函數。第一個參 數為信號值,第三個參數沒有使用(posix沒有規范使用該參數的標準),第二個參數是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值, 參數所指向的結構如下:


            1. siginfo_t {
            2.                   int si_signo; /* 信號值,對所有信號有意義*/
            3.                   int si_errno; /* errno值,對所有信號有意義*/
            4.                   int si_code; /* 信號產生的原因,對所有信號有意義*/
            5.         union{ /* 聯合數據結構,不同成員適應不同信號 */ 
            6.           //確保分配足夠大的存儲空間
            7.           int _pad[SI_PAD_SIZE];
            8.           //對SIGKILL有意義的結構
            9.           struct{
            10.               ...
            11.               }...
            12.             ... ...
            13.             ... ... 
            14.           //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結構
            15.               struct{
            16.               ...
            17.               }...
            18.             ... ...
            19.             }
            20.       }

            注:為了更便于閱讀,在說明問題時常把該結構表示為附錄2所表示的形式。

            siginfo_t結構中的聯合數據成員確保該結構適應所有的信號,比如對于實時信號來說,則實際采用下面的結構形式:


            1. typedef struct {
            2.         int si_signo;
            3.         int si_errno;            
            4.         int si_code;            
            5.         union sigval si_value;    
            6.         } siginfo_t;

            結構的第四個域同樣為一個聯合數據結構:


            1. union sigval {
            2.         int sival_int;        
            3.         void *sival_ptr;    
            4.         }

            采用聯合數據結構,說明siginfo_t結構中的si_value要么持有一個4字節的整數值,要么持有一個指針,這就構成了與信號 相關的數據。在信號的處理函數中,包含這樣的信號相關數據指針,但沒有規定具體如何對這些數據進行操作,操作方法應該由程序開發人員根據具體任務事先約 定。

            前面在討論系統調用sigqueue發送信號時,sigqueue的第三個參數就是sigval聯合數據結構,當調用sigqueue 時,該數據結構中的數據就將拷貝到信號處理函數的第二個參數中。這樣,在發送信號同時,就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發是非常 有意義的。

            信號參數的傳遞過程可圖示如下:


             

            3、sa_mask指定在信號處理程序執行過程中,哪些信號應當被阻塞。缺省情況下當前信號本身被阻塞,防止信號的嵌套發送,除非指定SA_NODEFER或者SA_NOMASK標志位。

            注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數執行過程中由sa_mask指定的信號才被阻塞。

            4、sa_flags中包含了許多標志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標志位。另一個比較重要的標志位是 SA_SIGINFO,當設定了該標志位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該為sigaction結構中的 sa_sigaction指定處理函數,而不應該為sa_handler指定信號處理函數,否則,設置該標志變得毫無意義。即使為 sa_sigaction指定了信號處理函數,如果不設置SA_SIGINFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信 息的訪問都將導致段錯誤(Segmentation fault)。

            注:很多文獻在闡述該標志位時都認為,如果設置了該標志位,就必須定義三參數信號處理函數。實際不是這樣的,驗證方法很簡單:自己實現 一個單一參數信號處理函數,并在程序中設置該標志位,可以察看程序的運行結果。實際上,可以把該標志位看成信號是否傳遞參數的開關,如果設置該位,則傳遞 參數;否則,不傳遞參數。

            回頁首

            六、信號集及信號集操作函數:

            信號集被定義為一種數據類型:


            1. typedef struct {
            2.             unsigned long sig[_NSIG_WORDS]
            3.             } sigset_t

            信號集用來描述信號的集合,linux所支持的所有信號可以全部或部分的出現在信號集中,主要與信號阻塞相關函數配合使用。下面是為信號集操作定義的相關函數:


            1. #include <signal.h>
            2. int sigemptyset(sigset_t *set)
            3. int sigfillset(sigset_t *set)
            4. int sigaddset(sigset_t *set, int signum)
            5. int sigdelset(sigset_t *set, int signum)
            6. int sigismember(const sigset_t *set, int signum)
            7. sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集里面的所有信號被清空;
            8. sigfillset(sigset_t *set)調用該函數后,set指向的信號集中將包含linux支持的64種信號;
            9. sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;
            10. sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;
            11. sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中


            七、信號阻塞與信號未決:

            每個進程都有一個用來描述哪些信號遞送到進程時將被阻塞的信號集,該信號集中的所有信號在遞送到進程后都將被阻塞。下面是與信號阻塞相關的幾個函數:


            1. #include <signal.h>
            2. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset))
            3. int sigpending(sigset_t *set));
            4. int sigsuspend(const sigset_t *mask))

            sigprocmask()函數能夠根據參數how來實現對信號集的操作,操作主要有三種:

            參數how進程當前信號集
            SIG_BLOCK在進程當前阻塞信號集中添加set指向信號集中的信號
            SIG_UNBLOCK如果進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
            SIG_SETMASK更新進程阻塞信號集為set指向的信號集

            sigpending(sigset_t *set))獲得當前已遞送到進程,卻被阻塞的所有信號,在set指向的信號集中返回結果。

            sigsuspend(const sigset_t *mask))用于在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 并暫停進程執行,直到收到信號為止。sigsuspend 返回后將恢復調用之前的信號掩碼。信號處理函數完成后,進程將繼續執行。該系統調用始終返回-1,并將errno設置為EINTR。

            附錄1:結構itimerval:


            1. struct itimerval {
            2.                 struct timeval it_interval; /* next value */
            3.                 struct timeval it_value; /* current value */
            4.             };
            5.             struct timeval {
            6.                 long tv_sec; /* seconds */
            7.                 long tv_usec; /* microseconds */
            8.             };

            附錄2:三參數信號處理函數中第二個參數的說明性描述:


            1. siginfo_t {
            2. int si_signo; /* 信號值,對所有信號有意義*/
            3. int si_errno; /* errno值,對所有信號有意義*/
            4. int si_code; /* 信號產生的原因,對所有信號有意義*/
            5. pid_t si_pid; /* 發送信號的進程ID,對kill(2),實時信號以及SIGCHLD有意義 */
            6. uid_t si_uid; /* 發送信號進程的真實用戶ID,對kill(2),實時信號以及SIGCHLD有意義 */
            7. int si_status; /* 退出狀態,對SIGCHLD有意義*/
            8. clock_t si_utime; /* 用戶消耗的時間,對SIGCHLD有意義 */
            9. clock_t si_stime; /* 內核消耗的時間,對SIGCHLD有意義 */
            10. sigval_t si_value; /* 信號值,對所有實時有意義,是一個聯合數據結構,
            11.                           /*可以為一個整數(由si_int標示,也可以為一個指針,由si_ptr標示)*/
            12.     
            13. void * si_addr; /* 觸發fault的內存地址,對SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號有意義*/
            14. int si_band; /* 對SIGPOLL信號有意義 */
            15. int si_fd; /* 對SIGPOLL信號有意義 */
            16. }

            實際上,除了前三個元素外,其他元素組織在一個聯合結構中,在聯合數據結構中,又根據不同的信號組織成不同的結構。注釋中提到的對某種信號有意義指的是,在該信號的處理函數中可以訪問這些域來獲得與信號相關的有意義的信息,只不過特定信號只對特定信息感興趣而已。


            參考資料

            1. linux內核源代碼情景分析(上),毛德操、胡希明著,浙江大學出版社,當要驗證某個結論、想法時,最好的參考資料;
            2. UNIX環境高級編程,作者:W.Richard Stevens,譯者:尤晉元等,機械工業出版社。對信號機制的發展過程闡述的比較詳細。
            3. signal、sigaction、kill等手冊,最直接而可靠的參考資料。
            4. http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了許多系統調用、庫函數等的在線指南。
            5. http://www.opengroup.org/onlinepubs/007904975/可以在這里對許多關鍵函數(包括系統調用)進行查詢,非常好的一個網址。
            6. http://unix.org/whitepapers/reentrant.html對函數可重入進行了闡述。
            7. http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對實時信號給出了相當好的描述。
            精品综合久久久久久88小说| 国产精品久久久久久久久软件 | 爱做久久久久久| 99久久国产免费福利| 欧美成a人片免费看久久| 麻豆精品久久久久久久99蜜桃| 九九精品99久久久香蕉| AA级片免费看视频久久| 亚洲精品无码专区久久同性男| 久久久亚洲欧洲日产国码二区| 很黄很污的网站久久mimi色| 国内精品伊人久久久久777| 99久久国产亚洲高清观看2024| 久久这里都是精品| 日本久久久精品中文字幕| 成人综合久久精品色婷婷| 99国产精品久久| 欧美伊人久久大香线蕉综合| 久久香蕉一级毛片| 久久久久久午夜成人影院| 色综合久久久久综合99| 国产91久久综合| 国产精品久久久久久久久免费| 2019久久久高清456| 色天使久久综合网天天 | 亚洲国产成人精品91久久久 | 久久狠狠一本精品综合网| 久久国产精品国产自线拍免费| 国内精品伊人久久久久777| 香蕉久久影院| 色偷偷91久久综合噜噜噜噜| 久久久综合香蕉尹人综合网| 久久久久久亚洲精品不卡| 精品乱码久久久久久夜夜嗨| 精品国产91久久久久久久| 成人国内精品久久久久一区| 精品无码久久久久久尤物| 日韩av无码久久精品免费| 无码专区久久综合久中文字幕 | 国产午夜精品理论片久久影视| 久久一日本道色综合久久|