Linux 信號(signal)

對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。信號,為 Linux 提供了一種處理異步事件的方法。比如,終端用戶輸入了 ctrl+c 來中斷程序,會通過信號機制停止一個程序。

信號概述

  1. 信號的名字和編號:
    每個信號都有一個名字和編號,這些名字都以“SIG”開頭,例如“SIGIO ”、“SIGCHLD”等等。
    信號定義在signal.h頭文件中,信號名都定義為正整數。
    具體的信號名稱可以使用kill -l來查看信號的名字以及序號,信號是從1開始編號的,不存在0號信號。kill對于信號0又特殊的應用。

    信號的名稱

  2. 信號的處理:
    信號的處理有三種方法,分別是:忽略、捕捉和默認動作

  • 忽略信號,大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是 SIGKILLSIGSTOP)。因為他們向內核和超級用戶提供了進程終止和停止的可靠方法,如果忽略了,那么這個進程就變成了沒人能管理的的進程,顯然是內核設計者不希望看到的場景
  • 捕捉信號,需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。
  • 系統默認動作,對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。不過,對系統來說,大部分的處理方式都比較粗暴,就是直接殺死該進程。
    具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。在此,我就不詳細展開了,需要查看的,可以自行查看。也可以參考 《UNIX 環境高級編程(第三部)》的 P251——P256中間對于每個信號有詳細的說明。

了解了信號的概述,那么,信號是如何來使用呢?

其實對于常用的 kill 命令就是一個發送信號的工具,kill 9 PID來殺死進程。比如,我在后臺運行了一個 top 工具,通過 ps 命令可以查看他的 PID,通過 kill 9 來發送了一個終止進程的信號來結束了 top 進程。如果查看信號編號和名稱,可以發現9對應的是 9) SIGKILL,正是殺死該進程的信號。而以下的執行過程實際也就是執行了9號信號的默認動作——殺死進程。

kill 殺死進程

對于信號來說,最大的意義不是為了殺死信號,而是實現一些異步通訊的手段,那么如何來自定義信號的處理函數呢?

信號處理函數的注冊

信號處理函數的注冊不只一種方法,分為入門版和高級版

  1. 入門版:函數signal
  2. 高級版:函數sigaction

信號處理發送函數

信號發送函數也不止一個,同樣分為入門版和高級版
1.入門版:kill
2.高級版:sigqueue

信號注冊函數——入門版

在正式開始了解這兩個函數之前,可以先來思考一下,處理中斷都需要處理什么問題。
按照我們之前思路來看,可以發送的信號類型是多種多樣的,每種信號的處理可能不一定相同,那么,我們肯定需要知道到底發生了什么信號。
另外,雖然我們知道了系統發出來的是哪種信號,但是還有一點也很重要,就是系統產生了一個信號,是由誰來響應?
如果系統通過 ctrl+c 產生了一個 SIGINT(中斷信號),顯然不是所有程序同時結束,那么,信號一定需要有一個接收者。對于處理信號的程序來說,接收者就是自己。

開始的時候,先來看看入門版本的信號注冊函數,他的函數原型如下:
signal 的函數原型

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

根據函數原型可以看出由兩部分組成,一個是真實處理信號的函數,另一個是注冊函數了。
對于sighandler_t signal(int signum, sighandler_t handler);函數來說,signum 顯然是信號的編號,handler 是中斷函數的指針。
同樣,typedef void (*sighandler_t)(int);中斷函數的原型中,有一個參數是 int 類型,顯然也是信號產生的類型,方便使用一個函數來處理多個信號。我們先來看看簡單一個信號注冊的代碼示例吧。

#include<signal.h>
#include<stdio.h>
#include <unistd.h>

//typedef void (*sighandler_t)(int);
void 
handler(int signum)
{
    if(signum == SIGIO)
        printf("SIGIO   signal: %d\n", signum);
    else if(signum == SIGUSR1)
        printf("SIGUSR1   signal: %d\n", signum);
    else
        printf("error\n");
}

int 
main(void)
{
    //sighandler_t signal(int signum, sighandler_t handler);
    signal(SIGIO, handler);
    signal(SIGUSR1, handler);
    printf("%d  %d\n", SIGIO, SIGUSR1);
    for(;;)
    {
        sleep(10000);
    }
    return 0;
}

我們先使用 kill 命令發送信號給之前所寫的程序,關于這個命令,我們后面再談。


通過 kill 命令發送信號
程序接收到的信號的處理結果

簡單的總結一下,我們通過 signal 函數注冊一個信號處理函數,分別注冊了兩個信號(SIGIO 和 SIGUSER1);隨后主程序就一直“長眠”了。
通過 kill 命令發送信號之前,我們需要先查看到接收者,通過 ps 命令查看了之前所寫的程序的 PID,通過 kill 函數來發送。
對于已注冊的信號,使用 kill 發送都可以正常接收到,但是如果發送了未注冊的信號,則會使得應用程序終止進程。

那么,已經可以設置信號處理函數了,信號的處理還有兩種狀態,分別是默認處理和忽略,這兩種設置很簡單,只需要將 handler 設置為 SIG_IGN(忽略信號)或 SIG_DFL(默認動作)即可。

在此還有兩個問題需要說明一下:

  1. 當執行一個程序時,所有信號的狀態都是系統默認或者忽略狀態的。除非是 調用exec進程忽略了某些信號。exec 函數將原先設置為要捕捉的信號都更改為默認動作,其他信號的狀態則不會改變 。
    2.當一個進程調動了 fork 函數,那么子進程會繼承父進程的信號處理方式。

入門版的信號注冊還是比較簡單的,只需要一句注冊和一個處理函數即可,那么,接下來看看,如何發送信號吧。

信號發送函數——入門版

kill 的函數原型

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

正如我之前所說的,信號的處理需要有接受者,顯然發送者必須要知道發給誰,根據 kill 函數的遠行可以看到,pid 就是接受者的 pid,sig 則是發送的信號的類型。從原型來看,發送信號要比接受信號還要簡單些,那么我們直接上代碼吧~~!Show me the code!!!

#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    if(3 != argc)
    {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number>\n", argv[0]);
        return -1;
    }
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);
    //int kill(pid_t pid, int sig);
    if(pid > 0 && sig > 0)
    {
        kill(pid, sig);
    }
    else
    {
        printf("Target_PID or Signal_Number MUST bigger than 0!\n");
    }
    
    return 0;
}
發送信號
接收信號的結果

總結一下:
根據以上的結果可看到,基本可以實現了信號的發送,雖然不能直接發送信號名稱,但是通過信號的編號,可以正常的給程序發送信號了,也是初步實現了信號的發送流程。

關于 kill 函數,還有一點需要額外說明,上面的程序限定了 pid 必須為大于0的正整數,其實 kill 函數傳入的 pid 可以是小于等于0的整數。
pid > 0:將發送個該 pid 的進程
pid == 0:將會把信號發送給與發送進程屬于同一進程組的所有進程,并且發送進程具有權限想這些進程發送信號。
pid < 0:將信號發送給進程組ID 為 pid 的絕對值得,并且發送進程具有權限向其發送信號的所有進程
pid == -1:將該信號發送給發送進程的有權限向他發送信號的所有進程。(不包括系統進程集中的進程)

關于信號,還有更多的話題,比如,信號是否都能夠準確的送達到目標進程呢?答案其實是不一定,那么這就有了可靠信號和不可靠信號

可靠信號和不可靠信號

不可靠信號:信號可能會丟失,一旦信號丟失了,進程并不能知道信號丟失
可靠信號:也是阻塞信號,當發送了一個阻塞信號,并且該信號的動作時系統默認動作或捕捉該信號,如果信號從發出以后會一直保持未決的狀態,直到該進程對此信號解除了阻塞,或將對此信號的動作更改為忽略。
對于信號來說,信號編號小于等于31的信號都是不可靠信號,之后的信號為可卡信號,系統會根據有信號隊列,將信號在遞達之前進行阻塞。

信號的阻塞和未決是通過信號的狀態字來管理的,該狀態字是按位來管理信號的狀態。每個信號都有獨立的阻塞字,規定了當前要阻塞地達到該進程的信號集。

信號阻塞狀態字(block),1代表阻塞、0代表不阻塞;信號未決狀態字(pending)的1代表未決,0代表信號可以抵達了;它們都是每一個bit代表一個信號

  • 阻塞和未決是如何工作的?
    比如向進程發送SIGINT信號,內核首先會判斷該進程的信號阻塞狀態字是否阻塞狀態,如果該信號被設置為阻塞的狀態,也就是阻塞狀態字對應位為1,那么信號未決狀態字(pending)相應位會被內核設置為1;如果該信號阻塞解除了,也就是阻塞狀態字設置為了0,那么信號未決狀態字(pending)相應位會被內核設置為0,表示信號此時可以抵達了,也就是可以接收該信號了。
    阻塞狀態字用戶可以讀寫,未決狀態字用戶只能讀,是由內核來設置表示信號遞達狀態的。
    PS:這里額外說明以下,只有支持了 POSIX.1實時擴展的系統才支持排隊的功能(也就阻塞狀態下多次同一信號發送給某一進程可以得到多次,而不是一次)。

  • 關于進程關于信號的阻塞狀態字的設置
    可以通過int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);函數來獲取或者設置。

該函數管理信號,是通過信號集的數據結構來進行管理的,信號集可以通過以下的函數進行管理。
信號集操作函數(狀態字表示)

#include <signal.h>
       int sigemptyset(sigset_t *set);  //初始化 set 中傳入的信號集,清空其中所有信號
       int sigfillset(sigset_t *set);  //把信號集填1,讓 set 包含所有的信號
       int sigaddset(sigset_t *set, int signum);//把信號集對應位置為1
       int sigdelset(sigset_t *set, int signum);//吧信號集對應位置為0
       int sigismember(const sigset_t *set, int signum);//判斷signal是否在信號集

對于信號集分配好內存空間,需要使用初始化函數來初始化。初始化完成后,可以在該集合中添加、刪除特定的信號。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
其中 how 變量決定了是如何操作該狀態字。
SIG_BLOCK:set包含了我們希望添加到當前信號阻塞字的信號,相當于mask=mask|set
SIG_UNBLOCK:set包含了我們希望從當前信號阻塞字中解除阻塞的信號,相當于mask=mask&~set
SIG_SETMASK:設置當前信號阻塞字為set所指的值,相當于mask=set

pending是由內核來根據block設置的,只可以讀取其中的數據,來段判斷信號是否會遞達。通過設置block可以將希望阻塞的信號進行阻塞,對應的pending會由內核來設置

設置信號阻塞、未達的步驟:

  1. 分配內存空間sigset sigset bset;
  2. 置空sigemptyset(&bset);
  3. 添加信號sigaddset(&bset, SIGINT);
  4. 添加其他需要管理的信號....
  5. 設置信號集中的信號處理方案(此處為解除阻塞)sigprocmask(SIG_UNBLOCK, &bset, NULL);
  • 簡化版設置阻塞狀態字
#include <signal.h>
int sigpending(sigset_t *set);

這個函數使用很簡單,對于調用他的進程來說,其中信號集中的信號是阻塞不能遞送的,那么,也就一定會是當前未決的。

  • 原子操作的信號阻塞字的恢復并進入休眠狀態
#include <signal.h>
int sigsuspend(const sigset_t *mask);

為何會出現原子性的解除阻塞的函數呢?
因為,當信號被阻塞的時候,產生了信號,那么該信號的遞送就要推遲到這個信號被解除了阻塞為止。如果此時,應用程序正好處在,解除 SIGINT 的阻塞和 pause 之間,那么此時,會產生問題,可能永遠 pause 不能夠等到SIGINT 信號來打斷他,造成程序永久阻塞在 pause 處。
為了解決這個問題,,需要在一個原子性的操作來恢復信號的屏蔽字,然后才能讓進程進入休眠狀態,以保證不會出現上述的問題。

進程的信號屏蔽字設置為

信號注冊函數——高級版

我們已經成功完成了信號的收發,那么為什么會有高級版出現呢?其實之前的信號存在一個問題就是,雖然發送和接收到了信號,可是總感覺少些什么,既然都已經把信號發送過去了,為何不能再攜帶一些數據呢?
正是如此,我們需要另外的函數來通過信號傳遞的過程中,攜帶一些數據。咱么先來看看發送的函數吧。

sigaction 的函數原型

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

struct sigaction {
   void       (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用
   sigset_t   sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。
   int        sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據
 };
//回調函數句柄sa_handler、sa_sigaction只能任選其一

這個函數的原版幫助信息,可以通過man sigaction來查看。

sigaction 是一個系統調用,根據這個函數原型,我們不難看出,在函數原型中,第一個參數signum應該就是注冊的信號的編號;第二個參數act如果不為空說明需要對該信號有新的配置;第三個參數oldact如果不為空,那么可以對之前的信號配置進行備份,以方便之后進行恢復。

在這里額外說一下struct sigaction結構體中的 sa_mask 成員,設置在其的信號集中的信號,會在捕捉函數調用前設置為阻塞,并在捕捉函數返回時恢復默認原有設置。這樣的目的是,在調用信號處理函數時,就可以阻塞默寫信號了。在信號處理函數被調用時,操作系統會建立新的信號阻塞字,包括正在被遞送的信號。因此,可以保證在處理一個給定信號時,如果這個種信號再次發生,那么他會被阻塞到對之前一個信號的處理結束為止。

sigaction 的時效性:當對某一個信號設置了指定的動作的時候,那么,直到再次顯式調用 sigaction并改變動作之前都會一直有效。

關于結構體中的 flag 屬性的詳細配置,在此不做詳細的說明了,只說明其中一點。如果設置為 SA_SIGINFO 屬性時,說明了信號處理程序帶有附加信息,也就是會調用 sa_sigaction 這個函數指針所指向的信號處理函數。否則,系統會默認使用 sa_handler 所指向的信號處理函數。在此,還要特別說明一下,sa_sigaction 和 sa_handler 使用的是同一塊內存空間,相當于 union,所以只能設置其中的一個,不能兩個都同時設置。

關于void (*sa_sigaction)(int, siginfo_t *, void *);處理函數來說還需要有一些說明。void* 是接收到信號所攜帶的額外數據;而struct siginfo這個結構體主要適用于記錄接收信號的一些相關信息。

 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

其中的成員很多,si_signo 和 si_code 是必須實現的兩個成員。可以通過這個結構體獲取到信號的相關信息。
關于發送過來的數據是存在兩個地方的,sigval_t si_value這個成員中有保存了發送過來的信息;同時,在si_int或者si_ptr成員中也保存了對應的數據。

那么,kill 函數發送的信號是無法攜帶數據的,我們現在還無法驗證發送收的部分,那么,我們先來看看發送信號的高級用法后,我們再來看看如何通過信號來攜帶數據吧。

信號發送函數——高級版
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

使用這個函數之前,必須要有幾個操作需要完成

  1. 使用 sigaction 函數安裝信號處理程序時,制定了 SA_SIGINFO 的標志。
  2. sigaction 結構體中的 sa_sigaction 成員提供了信號捕捉函數。如果實現的時 sa_handler 成員,那么將無法獲取額外攜帶的數據。

sigqueue 函數只能把信號發送給單個進程,可以使用 value 參數向信號處理程序傳遞整數值或者指針值。

sigqueue 函數不但可以發送額外的數據,還可以讓信號進行排隊(操作系統必須實現了 POSIX.1的實時擴展),對于設置了阻塞的信號,使用 sigqueue 發送多個同一信號,在解除阻塞時,接受者會接收到發送的信號隊列中的信號,而不是直接收到一次。

但是,信號不能無限的排隊,信號排隊的最大值受到SIGQUEUE_MAX的限制,達到最大限制后,sigqueue 會失敗,errno 會被設置為 EAGAIN。

那么我們來嘗試一下,發送一個攜帶有額外數據的信號吧。
Show me the code!!
接收端

#include<signal.h>
#include<stdio.h>
#include <unistd.h>

//void (*sa_sigaction)(int, siginfo_t *, void *);
void handler(int signum, siginfo_t * info, void * context)
{
    if(signum == SIGIO)
        printf("SIGIO   signal: %d\n", signum);
    else if(signum == SIGUSR1)
        printf("SIGUSR1   signal: %d\n", signum);
    else
        printf("error\n");
    
    if(context)
    {
        printf("content: %d\n", info->si_int);
        printf("content: %d\n", info->si_value.sival_int);
    }
}

int main(void)
{
    //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    struct sigaction act;
    
    /*
     struct sigaction {
     void     (*sa_handler)(int);
     void     (*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t   sa_mask;
     int        sa_flags;
     };
     */
    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO;
    
    sigaction(SIGIO, &act, NULL);
    sigaction(SIGUSR1, &act, NULL);
    for(;;)
    {
        sleep(10000);
    }
    return 0;
}

發送端

#include <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    if(4 != argc)
    {
        printf("[Arguments ERROR!]\n");
        printf("\tUsage:\n");
        printf("\t\t%s <Target_PID> <Signal_Number> <content>\n", argv[0]);
        return -1;
    }
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    if(pid > 0 && sig > 0)
    {
        //int sigqueue(pid_t pid, int sig, const union sigval value);
        union sigval val;
        val.sival_int = atoi(argv[3]);
        printf("send: %d\n", atoi(argv[3]));
        sigqueue(pid, sig, val);
    }
    else
    {
        printf("Target_PID or Signal_Number MUST bigger than 0!\n");
    }
    
    return 0;
}

接收到的信號和信息
發送的信號和數據
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 又來到了一個老生常談的問題,應用層軟件開發的程序員要不要了解和深入學習操作系統呢? 今天就這個問題開始,來談談操...
    tangsl閱讀 4,148評論 0 23
  • 一、進程的創建和調度 相關概念: 最基礎的計算機動作被稱為指令(instruction)。 程序(program)...
    穹藍奧義閱讀 4,690評論 0 6
  • Linux 進程管理與程序開發 進程是Linux事務管理的基本單元,所有的進程均擁有自己獨立的處理環境和系統資源,...
    JamesPeng閱讀 2,484評論 1 14
  • 讀書后一個人留在了這個城市, 這座城市風很大, 孤獨的人總是晚回家, 每天忙忙碌碌, 奔波于上班、加班、吃飯、睡覺...
    阿民漫時光閱讀 288評論 0 0
  • 此段內容簡要來自自強學堂的教程詳情請查詢自強學堂 一、 后臺的運作流程 接收request請求 處理數據 獲取請求...
    coder_ben閱讀 5,249評論 6 56