1,pcb:進程式控制制塊結構體:/usr/src/linux headers 4.15.0 29/include/linux/sched.h 進程id:系統中每個進程有唯一的id,在c語言中用pid_t類型表示,是個非負整數。 進程狀態:就緒,運行,掛起,停止等狀態 描述虛擬地址空間的信息 描述控制終 ...
1,pcb:進程式控制制塊結構體:/usr/src/linux-headers-4.15.0-29/include/linux/sched.h
進程id:系統中每個進程有唯一的id,在c語言中用pid_t類型表示,是個非負整數。
進程狀態:就緒,運行,掛起,停止等狀態
描述虛擬地址空間的信息
描述控制終端的信息
進程執行時的當前工作目錄(current working directory)
umask掩碼
文件描述符表,包含很多指向file結構體的指針
和信號相關的信息
用戶id和組id
會話(session)和進程組
進程可以使用的資源上限(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(沒有子進程了,子進程都被回收完了)
- 調用時設置了WNOHANG並且還指定了1個或者多個子進程