linux 進程概念

1,pcb:進程控制塊結構體:/usr/src/linux-headers-4.15.0-29/include/linux/sched.h

  • 進程id:系統中每一個進程有惟一的id,在c語言中用pid_t類型表示,是個非負整數。linux

  • 進程狀態:就緒,運行,掛起,中止等狀態c++

  • 描述虛擬地址空間的信息shell

  • 描述控制終端的信息bash

  • 進程執行時的當前工做目錄(current working directory)微信

  • umask掩碼session

  • 文件描述符表,包含不少指向file結構體的指針函數

  • 和信號相關的信息學習

  • 用戶id和組id編碼

  • 會話(session)和進程組spa

  • 進程可使用的資源上限(Resource Limit)

    用【ulimit -a】查看:

    ys@ys:~$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 7743
    max locked memory       (kbytes, -l) 16384
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 7743
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited

2,環境變量

查看全部的環境變量:env

環境寫法:等號先後沒有空格

KEY=VALUE

經常使用的環境變量:

  • PATH:可執行文件的搜索路徑。好比能夠直接敲ls,那是由於ls的可執行文件在環境變量PATH裏,因此係統找獲得。可是好比a.out執行文件,就不能直接敲a.out,由於a.out不在PATH裏。
  • SHELL:當前使用的shell程序,一般是:/bin/bash
  • TERM:當前使用的終端類型,若是是圖像界面的話,通常爲:xterm-256color
  • LANG:當前的語言和字符編碼,通常爲:en_US.UTF-8
  • HOME:當前用戶所在的絕對路徑。

獲取環境變量:getenv

#include <stdlib.h>
char *getenv(const char *name);

返回值:

  • 成功:返回指針

  • 失敗:返回NULL

設置環境變量:setenv

#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);

刪除環境變量:unsetenv

#include <stdlib.h>
int unsetenv(const char *name);

設置環境變量和刪除環境變量通常不用系統函數,而是編輯【~/.bashrc】文件。

使用【exprot key=value】。

3,進程概念

建立進程:fork

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
  • 返回值
    • 失敗:-1
    • 成功:返回2次
      • 父進程中返回:子進程的ID
      • 子進程返回:0

得到當前進程本身id:getpid

得到當前進程的父進程的id:getppid

ss
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

理解fork的小例子:

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

int main(){
  //char* val = getenv("HOME");
  printf("begin...\n");
  pid_t pid = fork();
  printf("end...\n");
}

執行結果:

ys@ys:~/test$ ./en 
begin...
end...
ys@ys:~/test$ end...

從執行結果發下:

1,【begin】被打印出1次;【end】被打印出2次。

2,第二個【end】跑到了shell的後面。

分析:

  • 從執行結果1能夠看出來,fork後,從1個進程,又分出了一個子進程。而且子進程不是從程序的開頭開始執行,而是從fork後面開始執行,這個理解相當重要。
  • 執行結果2現象的解釋,根本緣由是父進程比子進程先執行結束了,致使了子進程成爲了孤兒進程。最開始shell進程把執行權交給程序,也就是父進程,當父進程結束後,shell又接管終端,但是這個時間點,子進程還沒結束,子進程的打印語句就把【end】打印到了shell後面。

下面的例子能夠了解,getpid和getppid的用法:

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

int main(){
  //char* val = getenv("HOME");
  printf("begin...\n");
  pid_t pid = fork();

  //child process
  if(pid == 0){
    printf("child: pid=%d, ppid=%d\n", getpid(), getppid());
  }
  //parent proces
  else if(pid > 0){
    printf("parent: pid=%d, child id:%d\n", getpid(), pid);
    sleep(1);//爲了讓子進程先結束
  }
  printf("end...\n");
}

查看進程的命名:ps

  • 選項a:顯示更多信息
  • 選項u:顯示用戶信息
  • 選項x:和選項a一塊兒使用
  • 選項j:能看到父進程
ys@ys:~$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.3 159928  7536 ?        Ss   13:53   0:02 /sbin/init spla
root         2  0.0  0.0      0     0 ?        S    13:53   0:00 [kthreadd]
ys        4029  0.0  0.0   4508   812 pts/0    S+   16:30   0:00 ./en
ys        4030  0.0  0.0   4508    80 pts/0    S+   16:30   0:00 ./en
ys        4043  0.1  0.2  29560  4936 pts/1    Ss   16:30   0:00 bash
ys        4051  0.0  0.1  44472  3700 pts/1    R+   16:30   0:00 ps aux

從下圖能夠看出子進程4030的父進程是4029,父進程4029的父進程是1671,進程1671是bash(shell)程序。因此的進程都是從進程1:【/sbin/init splash】衍生出來的。

ys@ys:~$ ps ajx
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 ?           -1 Ss       0   0:02 /sbin/init splash
 1661  1671  1671  1671 pts/0     4029 Ss    1000   0:00 bash
 1671  4029  4029  1671 pts/0     4029 S+    1000   0:00 ./en
 4029  4030  4029  1671 pts/0     4029 S+    1000   0:00 ./en
 1661  4043  4043  4043 pts/1     4055 Ss    1000   0:00 bash
 4043  4055  4055  4043 pts/1     4055 R+    1000   0:00 ps ajx

給進程發送信號:kill

使用【kill -l】查看信號列表,發現9號信號就殺死進程的信號。

ys@ys:~$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

由父進程建立5個子進程的例子:

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

int main(){
  int i = 0;
  pid_t pid = 0;
  for(i = 0; i < 5; ++i){
    pid = fork();
    if(pid == 0){
      //child process
      //printf("child: pid=%d, ppid=%d\n", getpid(), getppid());
      break;
    }
    else if (pid > 0){
      //parent process
      //printf("parent: pid=%d, ppid=%d\n", getpid(), getppid());
    }
  }

  sleep(i);
  printf("proces:%d will die:pid=%d,ppid=%d\n", i,getpid(), getppid());
  
}

執行結果:

ys@ys:~/test$ ./fo
proces:0 will die:pid=2139,ppid=2138
proces:1 will die:pid=2140,ppid=2138
proces:2 will die:pid=2141,ppid=2138
proces:3 will die:pid=2142,ppid=2138
proces:4 will die:pid=2143,ppid=2138
proces:5 will die:pid=2138,ppid=1704

要點在【break】和【sleep(i)】這2個地方。

不加break的話,子進程也會建立子進程。

觀察【sleep(i);]這行,執行到這行的時間點,若是i=0,則說明是第一建立的子進程;

若是i=4,說明是最後建立的子進程,若是i=5,說明是父進程。因此根據i的值,進行sleep,就可以實現先建立的子進程先執行完,後建立的後執行完,最後纔是父進程執行完。

在當進程調用別的程序:execl和execlp

#include <unistd.h>
int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
  • execl
    • path:目標程序的所在路徑和程序名。例:【/bin/ls】
    • arg:目標程序的參數。注意第一個參數必須是程序本身的名字。
    • NULL:最後一個參數必須是NULL,告訴它參數列表到這裏結束了。
    • 返回值:
      • 成功:就不返回了,直接走了,也就是說,調用execl的語句以後的代碼都不被執行了。
      • 失敗:-1
  • execlp:多了個p的含義就是:這個函數本身會去搜索環境變量PATH。
    • file:目標程序名,不用加路徑。例:【ls】
    • arg:目標程序的參數。注意第一個參數必須是程序本身的名字。
    • NULL:最後一個參數必須是NULL,告訴它參數列表到這裏結束了。
    • 返回值:
      • 成功:就不返回了,直接走了,也就是說,調用execl的語句以後的代碼都不被執行了。
      • 失敗:-1

調用ls的例子:

#include <unistd.h>
#include <stdio.h>
int main(){
  execl("/bin/ls", "ls", "-l", "--color=auto", NULL);
  perror("ls");
  printf("not back\n");
}

孤兒進程和殭屍進程

孤兒進程:父進死了,被init進程領養,變成孤兒進程。

殭屍進程:子進程死了,但父進程沒有回收子進程的資源(pcb(大結構體)),變成殭屍進程。

回收殭屍進程的方法:不能再用kill去殺,殺死父進程,讓init領養,init負責回收。

查看殭屍進程:

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 1704  2683  2683  1704 pts/0     2683 S+    1000   0:00 ./zo
 2683  2684  2683  1704 pts/0     2683 Z+    1000   0:00 [zo] <defunct>

發現有【Z+】和《defunct》就是殭屍進程。

回收子進程:wait函數

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
  • 做用:

    • 阻塞等待(若是子線程尚未死亡,則一直等到它死亡)
    • 回收子進程的資源(PCB結構體)
    • 查看死亡的緣由
  • wstatus:傳出參數,裏面有子進程死亡的緣由。

    查看wstatus的方法:使用下面2組宏。

    WIFEXITED(wstatus)
    WEXITSTATUS(wstatus)
    
    WIFSIGNALED(wstatus)
    WTERMSIG(wstatus)
    • 正常死亡:WIFEXITED(wstatus)返回真,使用WEXITSTATUS(wstatus)獲得退出狀態。

      退出狀態的具體含義:return 後面的數字;或者exit 括號裏的數字。

    • 非正常死亡(被信號殺死):WIFSIGNALED(wstatus)返回真,使用WTERMSIG(wstatus)獲得殺死它的信號(kill -l 顯示出來的數字)。

  • 返回值:

    • 成功:返回死亡子進程的pid
    • 失敗:-1

wait的例子:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <wait.h>
#include <stdlib.h>

int main(){
  int i = 0;
  pid_t pid = 0;

  pid = fork();
  if(pid == 0){
    //child process
    while(1){
      printf("child: pid=%d, ppid=%d\n", getpid(), getppid());
      sleep(3);
      //return 101;
      //exit(111);
      //sleep(2);
    }
  }
  else if (pid > 0){
    //parent process
    printf("parent: pid=%d, ppid=%d\n", getpid(), getppid());
    int wstatus;
    pid_t pid = wait(&wstatus);
    if(WIFEXITED(wstatus)){
      printf("child die pid=%d, status=%d\n",pid, WEXITSTATUS(wstatus));
    }
    if(WIFSIGNALED(wstatus)){
      printf("child die pid=%d, status=%d\n",pid, WTERMSIG(wstatus));
    }
  }
}

分析:

  • 正常死亡,代碼用return和exit來模擬的
  • 被信號殺死:在另外一終端裏使用【kill pid】後,返回的WTERMSIG(wstatus)爲15(SIGTERM);若是用【kill -9 pid】則WTERMSIG(wstatus)爲9(SIGKILL)

回收子進程:waitpid

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • pid
    • < -1:回收組ID爲-pid的子進程
    • -1:回收全部的子進程
    • 0:回收和調用進程的組id相同的組id的全部子進程。
    • 》0:回收指定pid的子進程
  • wstatus:和wait同樣
  • options:
    • 0:與wait功能相同。
    • WNOHANG:若是執行waitpid的時點,子進程還沒結束,會馬上返回,不阻塞。
  • 返回值:返回狀態發生了變化的子進程的id。
    • 調用時設置了WNOHANG而且還指定了1個或者多個子進程
      • 若是有子進程退出了,返回退出了的子進程的pid
      • 若是沒有子進程退出,返回0
    • 失敗:-1(沒有子進程了,子進程都被回收完了)

<font color="green">

c/c++ 學習互助QQ羣:877684253

本人微信:xiaoshitou5854

</font>

相關文章
相關標籤/搜索