linux終端關閉時為什么會導致在其上啟動的進程退出?

現象

經常在linux下開發的人應該都有這樣的經驗,就是在終端上啟動的程序,在關閉終端時,這個程序的進程也被一起關閉了。看下面這個程序,為了使進程永遠運行,在輸出helloworld后,循環調用sleep:

這里寫圖片描述

直接關閉這個終端,在另一個終端上查找該進程,已經找不到了:

這個行為看起來似乎是理所當然的,也符合人的第一感覺:”在終端上啟動的程序是屬于終端的,所以當關閉終端時,這個終端里的一包裹進程都一起被解決掉了”。但這種說法是不能使一個會思考且充滿好奇心的人信服的。

下面我們就從linux進程管理的細節來剖析其根本原因。

終端進程

linux系統是基于進程的,幾乎每個命令都可以在相應的目錄下找到它們的程序,執行一個命令相當于啟動一個或多個程序,終端也不例外,在我centos下面終端對應一個bash程序(不同操作系統終端的bash程序可能不一樣),它位于/usr/bin/下面:

這里寫圖片描述

每當打開一個終端都會啟動一個bash進程,我這里啟動了兩個終端,可以看到有兩個bash進程:

這里寫圖片描述

終端進程與啟動進程的關系

linux系統里面所有的進程的關系可以看做一個樹形結構,系統持續運行,進程的不斷啟動就是不斷fork的過程(fork是linux系統api,作用是復制自己來生成子進程),從系統啟動、初始化、登錄終端、到執行命令都是生成子進程的過程:

這里寫圖片描述

init進程是所有進程的祖先,它的pid(進程id)為1,ppid(父進程id)也為1,因為它沒有父進程,系統內的其他進程都是由它或者它的子進程fork而來。

我們在linux上作業的終端對應了一個bash進程,在其上運行的命令和程序都是bash的子進程,或由bash的子進程衍生。

用hw程序驗證一下,可以看到hw進程的父進程正好是bash進程:

這里寫圖片描述

但這并不能解釋為什么終端關閉了在上面運行的程序也跟著退出,因為在linux下,進程之間的關系并不像線程那樣,當主線程退出時,子線程一起被強制退出。進程之間沒有主次的區別,但有父子關系,而父子進程的運行是相對獨立的,一方的退出不會導致另一方退出。

進程session-揭開真相

在linux下,一個session是由一組進程組構成的,每個進程組又由多個進程構成。

在一個bash上運行的程序都歸屬于一個session(除非特別處理),而這個bash就是這個session的leader。每個session又可以關聯一個控制終端(Controlling Terminal)。

這里寫圖片描述

圖片:

  1. hw進程的ppid=5933,說明父進程為第一個bash,這個bash的父進程為gnome-ternimal進程,gnome-ternimal是centos可視化界面的終端管理進程,每打開一個終端,它都會啟動一個bash進程,而用戶的命令也是直接由bash進程執行的。
  2. hw程序和第一個bash同屬于一個session(sid=5933),這個sid等于bash的pid,所以第一個bash是這個session的leader。
  3. 圖片中還顯示了bash和hw進程擁有共同的終端設備pts/2,它是一種字符設備,不同于上面提到的gnome-ternimal進程。
  4. 當控制終端(對應gnome-ternimal)檢測到終端設備斷(對應pts/2)開連接時,會通知設備的控制進程,即發送SIGHUP信號給session leader(對應bash進程)。
  5. bash進程在收到SIGHUP后,將信號發給session下的所有進程,導致用戶啟動的進程退出。

下面通過strace命令來驗證以上結論:

  1. 跟蹤hw進程(命令意為跟蹤pid為6367的進程上與signal有關的系統調用):

    strace -e trace=signal -p 6367

  2. 跟蹤bash進程(命令意為跟蹤pid為5933的進程上與signal有關的系統調用):

    strace -e trace=signal -p 5933

  3. 關閉啟動hw程序的終端,觀察strace輸出.

hwd的strace如下,si_pid=5933說明是5933這個進程發了SIGHUP給它,也就是bash進程:

這里寫圖片描述

bash的strace略微復雜:

這里寫圖片描述

  1. kill(4294960929, SIGHUP)

    kill第一個參數是32位有符號整數,轉換成int就是-6367,當參數為負時表示發送給這個數絕對值的進程組,即pgrp=6367的所有進程,在上面的圖片中可以看到hw進程正好屬于該進程組。

  2. kill(5933, SIGHUP)

    5933是自己的pid,bash在第一次收到SIGHUP時先把信號發給session內其他進程,然后再次發送SIGHUP命令給自己,將自己殺死,后面的si_pid=5933也證實了這一點。

如何讓終端關閉時進程不退出

根據上面的結論,要使終端關閉時進程不退出,有以下幾種情況:

  1. 用戶進程攔截SIGHUP信號。
  2. 用戶進程和bash進程不在一個session。

下面依次驗證這兩種情況

攔截SIGHUP

修改hw程序,忽略SIGHUP信號:

signal(SIGHUP, SIG_IGN);
  • 1
  • 2

執行hw程序,并查看進程,可以看到hw進程和父進程bash:

這里寫圖片描述

關閉終端,在另一個終端查看進程:

這里寫圖片描述

bash進程已經退出,但hw進程還在,符合預期!!而且hw進程的ppid變成了1,說明hw在父進程bash退出后變成孤兒進程被init進程收養。

新建session&setsid

為了使用戶進程和bash不在同一個session,需要調用setsid方法,該方法的作用是新建一個新的session,并使自己成為leader。

// 先fork
int pid = fork();
if(pid > 0){// 父進程, 直接退出return 1;
}else if(pid == 0){// 子進程// 創建新的sessionsetsid();//printf("Hello World!\n");printf("sleeping...\n");while(1){sleep(1);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

調用setsid前先fork,因為若不fork,hw作為進程組的leader,是不允許重建session的,原因留給讀者自己思考。

編譯并執行hw,查看進程:

這里寫圖片描述

可以看到,相比之前,有幾個不同的地方:

  1. 程序啟動完,返回終端,hw切換到后臺運行。
  2. hw進程的父進程不再是bash,而是init進程。
  3. hw沒有關聯的終端設備(pts/2)。

關閉終端,看到bash已經消失,但對hw進程沒有任何影響:

這里寫圖片描述

更簡單的方法

  1. setsid命令,用setsid來啟動程序,這樣就不用修改任何代碼也可以做到使啟動的進程在新的session中,并且終端關閉時,進程不退出。

    setsid ./hw

  2. nohup命令,被nohup啟動的程序會忽略SIGHUP信號。

    nohup ./hw

其他

命令行中&的作用:

./hw &
  • 1
  • 2

&的作用是使程序在后臺運行,輸入fg命令又可以使程序切換到前臺。雖然在后臺運行,但并不能保證進程在終端關閉時不退出。

總結

簡而言之,終端在關閉時會發送SIGHUP給對應的bash進程,bash進程收到這個信號后首先將它發給session下面的進程,如果你的程序沒有對SIGHUP信號做特殊處理,那么進程就會隨著終端關閉而退出。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385040.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385040.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385040.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

二維數組做函數參數傳遞

#include<stdio.h> //#include<> //二位數組作為函數參數時&#xff0c;可以不指定第一個下標 void print_buf(int (*p)[3],int a,int b) //void print_buf(int p[][3],int a,int b) {int i,j;for(i 0 ; i < a; i){for(j 0; j < b; j){printf("p[%…

libevent源碼深度剖析

第一章 1&#xff0c;前言 Libevent是一個輕量級的開源高性能網絡庫&#xff0c;使用者眾多&#xff0c;研究者更甚&#xff0c;相關文章也不少。寫這一系列文章的用意在于&#xff0c;一則分享心得&#xff1b;二則對libevent代碼和設計思想做系統的、更深層次的分析&#xff…

函數返回指針類型(strchr函數)

#include<stdio.h> #include<string.h> char *mystrchr(char *s,char c) {while(*s){if(*s c){return s;}s;}return NULL; }int main() {char str[100] "hello world";//char* s strchr(str,a);char *s mystrchr(str,e);//返回ello world字符串 prin…

函數與指針

#include<stdio.h>int add(int a,int b) {return ab; }int main() {void *p(int,char *); //聲明了一個函數 &#xff0c;函數名為p&#xff0c;函數返回值為void*,函數的 void (*p)(int,char *);//定義了一個指向參數為int和char*返回值為void的函數指針//定義一個參數為…

使用指針在函數中交換數值

#include<stdio.h>void swap(int* a,int *b) {/*int temp *a;*a * b;*b temp;*/*a *b;*b *a - *b;*a *a - *b; }int main() {int a 10;int b 20;swap(&a,&b);printf("a %d,b %d\n",a,b);} 轉載于:https://www.cnblogs.com/wanghao-boke/p/1…

linux C 基于鏈表鏈的定時器

源碼如下&#xff1a;util_timer.h#ifndef LST_TIMER#define LST_TIMER#include <time.h>#include <sys/time.h>#include <stdlib.h>#include <signal.h>#define BUFFER_SIZE 64struct util_timer;/*struct client_data{sockaddr_in address;int sockf…

libevent學習筆記 一、基礎知識

一、libevent是什么libevent是一個輕量級的開源的高性能的事件觸發的網絡庫&#xff0c;適用于windows、linux、bsd等多種平臺&#xff0c;內部使用select、epoll、kqueue等系統調用管理事件機制。它被眾多的開源項目使用&#xff0c;例如大名鼎鼎的memcached等。特點&#xff…

漢字逆置

在計算機中&#xff0c;一個漢字用無法用1個字節來表示 #include<stdio.h> int main() {char buf[256] "你好";int len 0;while(buf[len]);len--;printf("%d\n",len);// 4一個漢字兩個字節 //printf("%p\n",buf);return 0; } 在windows下…

libevent項目分析(一) -- 準備階段

項目的簡介 我理解libevent是一個輕量級的&#xff0c;跨平臺高效的&#xff08;C語言實現&#xff09;事件驅動庫&#xff0c;類似于ACE項目中的ACE_Reactor&#xff0c;它實現了網絡通訊套接口I/O事件&#xff0c;定時器事件&#xff0c;信號事件的監聽和事件處理函數回調機制…

混合字符串字符數統計

因為漢字占一個以上字節&#xff0c;如何統計一個既有漢字又有字母的字符串呢&#xff1f; 漢字在計算機中的ASCII是以負數來與其他普通字符的ASCII區分的。 #include<stdio.h> int main() {char buf[256] "你好世界";printf("%d\n",buf[0]); //-60…

清除字符串空格

1.清除字符串中右邊的空格 從字符串尾部開始&#xff0c;找到非空格處&#xff0c;將下一個字符置為0即可。 //清除右邊空格 #include<stdio.h> int main() {char buf[] "hello world ";int len 0;//calculate the length of stringwhile(buf[len]);le…

淺談auto_ptr智能指針

引入智能指針&#xff1a;智能指針的實現原理&#xff1a; 資源分配即初始化RAII(Resource Acquisition Is Initialization)&#xff1a; 定義一個類來封裝資源的分配和釋放&#xff0c;在構造函數完成資源的分配和初始化&#xff0c;在析構函數完成資源的清理&#xff0c;可…

隨機數

隨機數產生器rand(),頭文件為#include<stdlib.h> #include<stdio.h> #include<stdlib.h>int main() {int value;int i;for(i 0; i < 10; i){value rand();printf("value %d\n",value);}return 0; } 運行結果&#xff1a; value 41 value 1…

多重繼承之虛繼承(主要是為了解決產生的數據冗余問題)

虛繼承 是面向對象編程中的一種技術&#xff0c;是指一個指定的基類&#xff0c;在繼承體系結構中&#xff0c;將其成員數據實例共享給也從這個基類型直接或間接派生的其它類。形式&#xff1a;在繼承定義中包含了virtual關鍵字的繼承關系&#xff0c;如下圖中&#xff0c;類A就…

通過syslog接收遠程日志

通過syslog接收遠程日志通過syslog接收遠程主機的日志&#xff0c;需要做一些環境配置。客戶機A通過syslog將日志信息發送到服務主機B&#xff08;或稱日志采集服務器&#xff09;。以下說明配置過程&#xff08;我的實驗環境是&#xff0c;客戶機A&#xff1a;Solaris 10&…

linux syslog服務器配置,自動發日志到另一臺日志服務器

1.客戶端:168.1.20.66修改/etc/syslog.conf 添加syslog.info 168.1.80.302.日志服務器:168.1.80.30修改/etc/sysconf/syslog 修改SYSLOGD_OPTIONS為 "-r -x -m 0" #-r表示允許接收外來的消息&#xff0c;-x表示不解析DNS, #-m 0表示時間戳標記間隔,如果指定只接…

Make文件(一)

基本規則&#xff1a; 目標&#xff1a;依賴 &#xff08;tab&#xff09;規則 目標&#xff1a;需要生成的目標文件 依賴&#xff1a;生成該目標所需的一些文件 規則&#xff1a;由依賴文件生成目標文件的手段 tab&#xff1a;每條規則前必須以tab開頭&#xff0c;使用空格不行…

移植驅動完畢后加載時的version magic報錯原因以及解決辦法

History:2012-02-17Author:yingru移植rt3070的AP驅動到裝有fedora14的PC機上時&#xff0c;模塊編譯完畢后&#xff0c;加載時提示invalid module format。PC機環境介紹&#xff1a;內核版本&#xff1a;2.6.35.6-45.fc14.i686命令行輸入dmesg查看最后的日志&#xff0c;發現如…

/proc 虛擬文件系統(實例)

Linux下有一個神奇的目錄/proc&#xff0c;經常會運行 cat /proc/cpuinfo 命令查看cpu信息&#xff0c;/proc下的確有cpuinfo文件&#xff0c;但是這個文件不是物理存在的&#xff0c;是軟件虛擬出來的&#xff0c;與普通文件不同&#xff0c;該文件是動態的。通過/proc可以實現…

內核模塊中對文件的讀寫

平時網絡部分的東西碰的多些&#xff0c;這塊一開始還真不知道怎么寫&#xff0c;因為肯定和在用戶空間下是不同的。google過后&#xff0c;得到以下答案。一般可以用兩種方法&#xff1a;第一種是用系統調用。第二種方法是filp->open()等函數。下面分別來說下這兩種方法。1…