Android跨進(jìn)程通信-信號(Signal)

信號的使用及原理

信號實(shí)質(zhì)上是一種軟中斷既们,既然是一種中斷居扒,就說明信號是異步的占调,信號接收函數(shù)不需要一直阻塞等待信號的到達(dá)暂题。
當(dāng)信號發(fā)出后,如果有地方注冊了這個(gè)信號究珊,就會執(zhí)行響應(yīng)函數(shù)薪者,如果沒有地方注冊這個(gè)信號,該信號就會被忽略剿涮。

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);  //信號注冊函數(shù)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //信號注冊函數(shù)
struct sigaction {
   void       (*sa_handler)(int); //信號處理程序言津,不接受額外數(shù)據(jù),SIG_IGN 為忽略取试,SIG_DFL 為默認(rèn)動作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序悬槽,能夠接受額外數(shù)據(jù)和sigqueue配合使用
   sigset_t   sa_mask;//阻塞關(guān)鍵字的信號集,可以再調(diào)用捕捉函數(shù)之前瞬浓,把信號添加到信號阻塞字初婆,信號捕捉函數(shù)返回之前恢復(fù)為原先的值。
   int        sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數(shù)據(jù)
 };
?
int kill(pid_t pid, int sig);  //信號發(fā)送函數(shù)
int sigqueue(pid_t pid, int sig, const union sigval value);   //信號發(fā)送函數(shù)
//……

注冊信號有兩個(gè)方法

  • signal()函數(shù):signal不支持傳遞信息猿棉,signum入?yún)樾盘柫堪跖眩琱andler入?yún)樾盘柼幚砗瘮?shù)

  • sigaction()函數(shù):sigaction支持傳遞信息,信息放在sigaction數(shù)據(jù)結(jié)構(gòu)中

信號發(fā)送

信號發(fā)送函數(shù)比較多萨赁,這里我列舉一下弊琴。

  • kill():用于向進(jìn)程或進(jìn)程組發(fā)送信號;
  • sigqueue():只能向一個(gè)進(jìn)程發(fā)送信號杖爽,不能向進(jìn)程組發(fā)送信號敲董;
  • alarm():用于調(diào)用進(jìn)程指定時(shí)間后發(fā)出SIGALARM信號紫皇;
  • setitimer():設(shè)置定時(shí)器,計(jì)時(shí)達(dá)到后給進(jìn)程發(fā)送SIGALRM信號臣缀,功能比alarm更強(qiáng)大坝橡;
  • abort():向進(jìn)程發(fā)送SIGABORT信號,默認(rèn)進(jìn)程會異常退出精置。
  • raise():用于向進(jìn)程自身發(fā)送信號计寇;

通過kill -l指令可以查看Android手機(jī)支持的信號,從下圖可以看到脂倦,總共有64個(gè)番宁,前31個(gè)信號是普通信號,后33個(gè)信號是實(shí)時(shí)信號赖阻,實(shí)時(shí)信號支持隊(duì)列蝶押,可以保證信號不會丟失。

前幾個(gè)信號的作用

1 SIGHUP 掛起
2 SIGINT 中斷
3 SIGQUIT 中斷
3 SIGQUIT 退出
4 SIGILL 非法指令
5 SIGTRAP 斷點(diǎn)或陷阱指令
6 SIGABRT abort發(fā)出的信號
7 SIGBUS 非法內(nèi)存訪問
8 SIGFPE 浮點(diǎn)異常
9 SIGKILL 殺進(jìn)程信息

當(dāng)我們調(diào)用信號發(fā)送函數(shù)后火欧,信號是怎么傳到注冊的方法調(diào)用中去的呢棋电?這里以kill()這個(gè)信號發(fā)送函數(shù)講解一下這個(gè)流程。

kill()函數(shù)會經(jīng)過系統(tǒng)調(diào)用方法sys_tkill()進(jìn)入內(nèi)核苇侵,sys_tkill是SYSCALL_DEFINE2這個(gè)方法來實(shí)現(xiàn)赶盔,這個(gè)方法的實(shí)現(xiàn)是一個(gè)宏定義。
我會從這個(gè)方法一路往底追蹤榆浓,這里我會忽略細(xì)節(jié)實(shí)現(xiàn)于未,只看關(guān)鍵部分的代碼。

//文件-->syscalls.h
asmlinkage long sys_kill(int pid, int sig);
?
//文件-->kernel/signal.c
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
  struct kernel_siginfo info;
?
  clear_siginfo(&info);
  info.si_signo = sig;
  info.si_errno = 0;
  info.si_code = SI_USER;
  info.si_pid = task_tgid_vnr(current);
  info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
?
  return kill_something_info(sig, &info, pid);
}
?
static int kill_something_info(int sig, struct kernel_siginfo *info, pid_t pid)
{
  int ret;
?
  if (pid > 0) {  
    ret = kill_pid_info(sig, info, find_vpid(pid));
    return ret;
  }
  ……
}
?
int kill_pid_info(int sig, struct kernel_siginfo *info, struct pid *pid)
{
  ……
  for (;;) {
        error = group_send_sig_info(sig, info, p, PIDTYPE_TGID);
    ……
  }
}
?
int group_send_sig_info(int sig, struct kernel_siginfo *info,
      struct task_struct *p, enum pid_type type)
{
  ……
   ret = do_send_sig_info(sig, info, p, type);
  return ret;
}
?
int do_send_sig_info(int sig, struct kernel_siginfo *info, struct task_struct *p,
      enum pid_type type)
{
  ……
  ret = send_signal(sig, info, p, type);
  return ret;
}
?
static int send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
      enum pid_type type)
{
  return __send_signal(sig, info, t, type, from_ancestor_ns);
}

我們從sys_kill函數(shù)一路追蹤陡鹃,最終調(diào)用了__send_signal函數(shù),我們接著看這個(gè)函數(shù)的實(shí)現(xiàn)烘浦。

//文件-->kernel/signal.c
static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
      enum pid_type type, int from_ancestor_ns)
{
  ……
out_set:
  signalfd_notify(t, sig);  //將信號發(fā)送給監(jiān)聽的fd
  sigaddset(&pending->signal, sig);
  complete_signal(sig, t, type);  //完成信號發(fā)送
ret:
  trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
  return ret;
}
?
static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
{
  struct signal_struct *signal = p->signal;
  struct task_struct *t;
?
  //尋找處理信號的線程
  if (wants_signal(sig, p))
    t = p;
  else if ((type == PIDTYPE_PID) || thread_group_empty(p))
    return;
  else {
    t = signal->curr_target;
    while (!wants_signal(sig, t)) {
      t = next_thread(t);
      if (t == signal->curr_target)
        return;
    }
    signal->curr_target = t;
  }
?
  //如果是SIGKILL信號,則殺掉線程組
  if (sig_fatal(p, sig) &&
      !(signal->flags & SIGNAL_GROUP_EXIT) &&
      !sigismember(&t->real_blocked, sig) &&
      (sig == SIGKILL || !p->ptrace)) {
    /*
     * This signal will be fatal to the whole group.
     */
    if (!sig_kernel_coredump(sig)) {
      /*
       * Start a group exit and wake everybody up.
       * This way we don't have other threads
       * running and doing things after a slower
       * thread has the fatal signal pending.
       */
      signal->flags = SIGNAL_GROUP_EXIT;
      signal->group_exit_code = sig;
      signal->group_stop_count = 0;
      t = p;
      do {
        task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
        sigaddset(&t->pending.signal, SIGKILL);
        signal_wake_up(t, 1);
      } while_each_thread(p, t);
      return;
    }
  }
  /*
   * The signal is already in the shared-pending queue.
   * Tell the chosen thread to wake up and dequeue it.
   */
  signal_wake_up(t, sig == SIGKILL);
  return;
}

可以看到萍鲸,信號最終被分發(fā)到了監(jiān)聽的fd中闷叉,交給了我們注冊的函數(shù)處理,從最后部分也可以看到脊阴,如果是SIGKILL信號片习,內(nèi)核會專門處理去殺進(jìn)程。

信號在Android中的使用場景

那么我們在來看一個(gè)Android系統(tǒng)中使用信號的場景:殺進(jìn)程蹬叭。從上面部分可以看到藕咏,SIGKILL信號是由內(nèi)核捕獲并處理的,我們看一下Android是怎么調(diào)用殺進(jìn)程的信號的吧秽五。

//文件-->Process.java
public static final void killProcess(int pid) {
    sendSignal(pid, SIGNAL_KILL); 
}
?
//文件-->android_util_Process.cpp
void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint sig) {
    if (pid > 0) {
        //打印Signal信息
        ALOGI("Sending signal. PID: %" PRId32 " SIG: %" PRId32, pid, sig);
        kill(pid, sig);
    }
}

可以看到孽查,當(dāng)我們調(diào)用Process的killProcess函數(shù)殺掉某個(gè)進(jìn)程時(shí),最終會調(diào)用到native方法kill()坦喘,入?yún)ig信號量就是SIGKILL盲再,這個(gè)kill()方法西设,就是我在上面講的信號量發(fā)送函數(shù),最終內(nèi)核會響應(yīng)我們的SIGKILL答朋,殺掉進(jìn)程贷揽。

4 信號(Signal)

4.1 什么是信號?

  • 信號是比較復(fù)雜的通信方式梦碗,用于通知接受進(jìn)程有某種事件發(fā)生禽绪,除了用于進(jìn)程間通信外,進(jìn)程還可以發(fā)送信號給進(jìn)程本身洪规;
  • Linux除了支持Unix早期信號語義函數(shù)sigal外印屁,還支持語義服務(wù)Posix.1標(biāo)準(zhǔn)的信號函數(shù)sigaction(實(shí)際上,該函數(shù)是基于BSD的斩例,BSD為了實(shí)現(xiàn)可靠信號機(jī)制雄人,有能夠統(tǒng)一對外接口,用sigaction函數(shù)重新實(shí)現(xiàn)了signal函數(shù))

4.2 信號的種類

5713484-b703c7840590eaeb.png
  • 每種信號類型都有對應(yīng)的信號處理程序(也叫信號的操作)念赶,就好像每個(gè)中斷都有一個(gè)中斷服務(wù)例程一樣础钠。
  • 大多數(shù)信號的默認(rèn)操作是結(jié)束接受信號的進(jìn)程;然而一個(gè)進(jìn)程通巢婷眨可以請求系統(tǒng)采取某些代替的操作
  • 各種代替操作是
  • 忽略信號珍坊。隨著這一選項(xiàng)的設(shè)置,進(jìn)程將忽略信號的出現(xiàn)正罢。有兩個(gè)信號不可以被忽略:SIGKILL,它將結(jié)束進(jìn)程:SIGSTOP驻民,它是作業(yè)控制機(jī)制的一部分翻具,將掛起作業(yè)的執(zhí)行。
  • 恢復(fù)信號的默認(rèn)操作
  • 執(zhí)行一個(gè)預(yù)先安排的信號處理函數(shù)回还。進(jìn)程可以登記特殊的信號處理函數(shù)裆泳。當(dāng)進(jìn)程收到信號時(shí),信號處理函數(shù)將像中斷服務(wù)例程一樣被調(diào)用柠硕,當(dāng)從信號處理函數(shù)返回時(shí)工禾,控制被返回給主程序,并且繼續(xù)正常執(zhí)行蝗柔。
  • 但是闻葵,信號和中斷有所不同。中斷的響應(yīng)和處理都發(fā)生在內(nèi)核空間癣丧,而信號的響應(yīng)發(fā)生在內(nèi)核空間槽畔,信號處理程序的執(zhí)行卻發(fā)生在用戶空間

那么什么時(shí)候檢測和響應(yīng)信號胁编?通常發(fā)生在兩種情況下:

  • 當(dāng)前進(jìn)程由于系統(tǒng)調(diào)用厢钧、中斷或異常而進(jìn)入內(nèi)核空間以后鳞尔,從內(nèi)核空間返回到用戶空間前戲
  • 當(dāng)前進(jìn)程在內(nèi)核進(jìn)入睡眠以后剛被喚醒的時(shí)候,由于檢測到信號的存在而提前返回到用戶空間

4.3 信號的本質(zhì)

  • 信號是在軟件層次上對中斷機(jī)制的一種模擬早直,在原理上寥假,一個(gè)進(jìn)程收到一個(gè)信號與處理器收到一個(gè)中斷請求可以說是一樣的。
  • 信號是異步的霞扬,一個(gè)進(jìn)程不必通過任何操作來等待信號的到達(dá)糕韧,事實(shí)上,進(jìn)程也不知道信號到底什么時(shí)候到達(dá)祥得。
  • 信號是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制兔沃,可以看作是異步通知,通知接收信號的進(jìn)程有哪些事情發(fā)生了级及。
  • 信號機(jī)制經(jīng)過POSIX實(shí)時(shí)擴(kuò)展后乒疏,功能更加強(qiáng)大,除了基本通知功能外饮焦,還可以傳遞附加信息

4.4 信號來源

  • 信號事件的發(fā)生有兩個(gè)來源:硬件來源(比如我們按下鍵盤或者其他硬件故障)怕吴;潤健來源,最常用發(fā)送信號的系統(tǒng)函數(shù)是kill县踢,raise,alarm和setitimer以及sigqueue函數(shù)转绷,軟件來源還包括一些非法運(yùn)算等操作。

4.5 關(guān)于信號處理機(jī)制的原理(內(nèi)核角度)

  • 內(nèi)核給一個(gè)進(jìn)程發(fā)送中斷信號硼啤,是在進(jìn)程所在的進(jìn)程表項(xiàng)的信號域設(shè)置對應(yīng)于該信號的位议经。
  • 如果信號發(fā)送給一個(gè)正在睡眠的進(jìn)程,那么要看該進(jìn)程進(jìn)入睡眠的優(yōu)先級谴返,如果進(jìn)程睡眠在可被中斷的優(yōu)先級上煞肾,則喚醒正在睡眠的進(jìn)程;否則僅設(shè)置進(jìn)程表中信號域相應(yīng)的位嗓袱,而不是喚醒進(jìn)程籍救。
  • 進(jìn)程檢查是否收到信號的時(shí)機(jī)是:一個(gè)進(jìn)程在即將從內(nèi)核態(tài)返回到用戶態(tài)時(shí);或者渠抹,在一個(gè)進(jìn)程進(jìn)入或離開一個(gè)適當(dāng)?shù)牡驼{(diào)度優(yōu)先級睡眠狀態(tài)時(shí)蝙昙。
  • 內(nèi)核處理一個(gè)進(jìn)程吸收的信號的時(shí)機(jī)是在一個(gè)進(jìn)程從內(nèi)核態(tài)返回用戶態(tài)時(shí)。所以梧却,當(dāng)一個(gè)進(jìn)程在內(nèi)核態(tài)下運(yùn)行時(shí)奇颠,軟中斷信號并不立即起作用,要等到將返回用戶態(tài)時(shí)才處理放航。進(jìn)程只有處理完信號才會返回用戶態(tài)大刊,進(jìn)程在用戶態(tài)下不會有未處理完的信號。
  • 內(nèi)核處理一個(gè)進(jìn)程收到的軟中斷信號是在該進(jìn)程的上下文中。因此缺菌,進(jìn)程必須處于運(yùn)行狀態(tài)葫辐。如果進(jìn)程收到一個(gè)要捕捉的信號,那么進(jìn)程從內(nèi)核態(tài)返回用戶態(tài)時(shí)執(zhí)行用戶定義的函數(shù)伴郁。
  • 而且執(zhí)行用戶定義的函數(shù)的方法很巧妙耿战,內(nèi)核是在用戶棧上創(chuàng)建一個(gè)新的層,該層中將返回地址的值設(shè)置成用戶定義的處理函數(shù)的地址焊傅,這樣進(jìn)程從內(nèi)核返回彈出棧頂時(shí)就返回到用戶定義的函數(shù)出剂陡,從函數(shù)返回再彈出棧頂時(shí),才返回原先進(jìn)入內(nèi)核的地方狐胎,接著原來的地方繼續(xù)運(yùn)行鸭栖。這樣做的原因是用戶定義的處理函數(shù)不能且不允許在內(nèi)核態(tài)下執(zhí)行。

4.6 信號的生命周期

5713484-025dbc4847a838ef.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末握巢,一起剝皮案震驚了整個(gè)濱河市晕鹊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暴浦,老刑警劉巖溅话,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歌焦,居然都是意外死亡飞几,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門独撇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屑墨,“玉大人,你說我怎么就攤上這事纷铣÷咽罚” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵关炼,是天一觀的道長。 經(jīng)常有香客問我匣吊,道長儒拂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任色鸳,我火速辦了婚禮社痛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘命雀。我一直安慰自己蒜哀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布吏砂。 她就那樣靜靜地躺著撵儿,像睡著了一般乘客。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淀歇,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天易核,我揣著相機(jī)與錄音,去河邊找鬼浪默。 笑死牡直,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纳决。 我是一名探鬼主播碰逸,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼阔加!你這毒婦竟也來了饵史?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤掸哑,失蹤者是張志新(化名)和其女友劉穎约急,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苗分,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厌蔽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摔癣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奴饮。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖择浊,靈堂內(nèi)的尸體忽然破棺而出戴卜,到底是詐尸還是另有隱情,我是刑警寧澤琢岩,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布投剥,位于F島的核電站,受9級特大地震影響担孔,放射性物質(zhì)發(fā)生泄漏江锨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一糕篇、第九天 我趴在偏房一處隱蔽的房頂上張望啄育。 院中可真熱鬧,春花似錦拌消、人聲如沸挑豌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氓英。三九已至侯勉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間债蓝,已是汗流浹背壳鹤。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饰迹,地道東北人芳誓。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像啊鸭,于是被迫代替她去往敵國和親锹淌。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容