進程的概念
程序:
存放在磁盤上的指令和數據的有序集合(文件)
靜態的
進程:
執行一個程序所分配的資源的總稱
進程是程序的一次執行過程
動態的,包括創建、調度、執行和消亡
進程包含的內容
進程包含:正文段(代碼段)、用戶數據段、系統數據段
程序包含:正文段(代碼段)、用戶數據段
系統數據包含:進程控制塊、CPU寄存器值、堆棧
進程控制塊(PCB)包含:
進程標識PID
進程用戶
進程狀態、優先級
文件描述符表
CPU寄存器值:
PC:program counter, 記錄着下一條執行指令的地址
堆棧:所有的局部變量都是在棧中存在的
進程的類型
交互進程:在shell下啟動。可以在前台運行,也可以在后台運行
批處理進程:和在終端無關,被提交到一個作業隊列中以便順序執行
守護進程:和終端無關,一直在后台運行
進程的狀態
運行態:進程正在運行,或者准備運行
等待態:進行在等待一個事件的發生或某種系統資源,又分為可中斷和不可中斷
停止態:進程被中止,收到信號后可繼續運行
死亡態:已終止的進程,但pcb沒有沒有被釋放
查看進程信息
ps:查看系統進程快照
ps -ef : 查看系統中所有的進程信息
ps aux: 比ps -ef 多一個當前的進程狀態信息,進程優先級
top:查看進程動態信息,每個3秒刷新一次
/proc:查看進程詳細信息,在proc目錄下以進程PID為名的目錄,其中status文件保存着進程的詳細信息,fd目錄保存這使用的文件
改變進程優先級
nice: 按用戶指定的優先級運行進程,nice的區間[-20, 19],默認值為0,值越小優先級越高,普通用戶能夠設置的最小值為0。
nice -n 2 ./a.out
renice:改變正在運行進程的優先級,普通用戶只能增加這個值。
renice -n 2 PID
前后台進程切換
jobs:查看后台進程
xdl@xdl-gj:~/C語言/thread$ ./a.out &
[1] 22441 //1表示作業號
xdl@xdl-gj:~/C語言/thread$ ./a.out &
[2] 22442
xdl@xdl-gj:~/C語言/thread$ jobs
[1]- 運行中 ./a.out &
[2]+ 運行中 ./a.out &
bg:降掛起的進程在后台運行
fg:把后台運行的進程放到前台運行
xdl@xdl-gj:~/C語言/thread$ fg 1
./a.out
^Z // ctrl +z 使進程掛起
[1]+ 已停止 ./a.out
xdl@xdl-gj:~/C語言/thread$ bg 1
[1]+ ./a.out &
創建進程
fork函數用於創建一個進程
#include <unistd.h>
pid_t fork(void);
創建新的進程,失敗時返回-1
成功時父進程返回子進程的進程號,子進程返回0
通過fork的返回值區分父進程和子進程
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; pid = fork(); printf("pid=%d\n", pid); if (pid < 0) { perror("fork"); return -1; } else if (pid == 0) { printf("child process: my pid is %d\n", getpid()); } else { printf("parent process: my pid is %d\n", getpid()); } return 0; } /* pid=6018 parent process: my pid is 6017 pid=0 child process: my pid is 6018 */
父子進程
子進程繼承父進程的內容
父子進程有獨立的地址空間,互不影響
若父進程先結束
子進程稱為孤兒進程,被init進程收養(linux啟動內核之后自動創建的用戶態進程PID為1)
子進程變成后台進程
若子進程先結束
父進程如果沒有及時回收,子進程變成僵屍進程
子進程從何處開始運行?
從fork下一句指令開始,並沒有執行fork語句
父子進程誰先執行?
不一定,一般父進程創建子進程后時間片沒有用完則繼續執行
父進程能否多次調用fork?子進程呢?
可以
結束進程
exit/_exit
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
結束當前進程並將status(低八位)返回
exit結束結束進程時會刷新(流)緩沖區
#include <stdio.h> #include <stdlib.h> // for exit #include <unistd.h> //for _exit int main(int argc, char *argv[]) { printf("this process will exit"); //exit(0);//會打印上一句 _exit(0);//不會打印 printf("never be displayed"); return 0; }
exec函數族
進程調用exec函數執行某個程序
進程當前內容被指定的程序替換
實現讓父子進程執行不同的程序
父進程創建子進程
子進程調用exec函數族
父進程不受影響
execl/execlp
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
成功執行指定的程序;失敗時返回EOF
path 執行的程序的名稱,包含路徑
arg... 傳遞給執行的程序的參數列表
file執行的程序的名稱,在PATH中查找
執行ls命令,顯示/etc目錄下所有文件的詳細信息
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid; pid = fork(); printf("pid=%d\n", pid); if (pid < 0) { perror("fork"); return -1; } else if (pid == 0) { printf("child process: my pid is %d\n", getpid()); if (execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL) < 0) { perror("execl"); return -1; } } else { printf("parent process: my pid is %d\n", getpid()); if (execlp("ls", "ls", "-al", "/etc", NULL) < 0 ) { perror("execlp"); return -1; } } return 0; } /* pid=6018 parent process: my pid is 6017 pid=0 child process: my pid is 6018 */
execv/execvp
#include <unistd.h>
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
成功時執行指定的程序;失敗時返回EOF
arg...封裝成指針數組的形式
char *arg[] = {"ls", "-a", "-l", "/etc", NULL};
if(execv("/bin/ls", arg) < 0){
perror("execv");
}
if(execvp("ls", arg) < 0){
perror("execvp");
}
system
#include <stflib.h>
int system(const char *command);
成功時返回命令command的返回值;失敗時返回EOF
當前進程等待command執行結束后才繼續執行
進程回收
子進程結束時由父進程回收
孤兒進程由init進程回收
若沒有及時回收會出現僵屍進程
wait函數
#include <unistd.h>
pid_t wait(int *status);
成功時返回回收的子進程的進程號;失敗時返回EOF
若子進程沒有結束,父進程一直阻塞
若有多個子進程,哪個先結束就先回收
status指定保存子進程返回值和結束方式的地址
status為NULL表示直接釋放子進程PCB,不接收返回值
wait pid函數
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
成功時返回回收的子進程的pid或0(表示子進程還沒有結束);失敗時返回EOF
pid可用於指定回收哪個子進程或任意子進程(-1表示任意子進程)
status指定用於保存子進程返回值和結束方式
option指定回收方式,0(阻塞方式)或WNOHANG(非阻塞)
進程返回值和結束方式
子進程通過exit/_exit/return 返回某個值(0-255)
父進程調用wait(&status)回收
WIFEXITED(status) : 判斷子進程是否正常結束
WEXITSTATUS(status): 獲取子進程返回值
WIFSIGALED(status): 判斷子進程是否被信號結束
WTERMSIG(status): 獲取結束子進程的信號類型
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int main(int argc, char *argv[]) { int status; pid_t pid; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid == 0) { sleep(1); exit(2); } else { wait(&status); printf("%d\n", status); } return 0; } /* 200 521 0010 0000 0000 低7為(0~7)表示進程結束的類型,0表示正常結束,非零表示信號結束 高8為(8~15)表示子進程正常結束時的返回值 */
守護進程
守護進程(Daemon)是Linux三種進程類型之一
通常在系統啟動時運行,系統關閉時結束
Linux系統中大量使用,很多服務程序以守護進程形式運行
守護進程的特點
始終在后台運行
獨立於任何終端
中期性的執行某種任務或等待處理特定事件
會話、控制終端
Linux以會話(session)、進程組的方式管理進程
每一個進程屬於一個進程組,父子進程屬於同一個進程組
會話是一個或多個進程組的集合。通常用戶打開一個終端時,系統會創建一個會話。所有通過該終端運行的進程都屬於這個會話
終端關閉時,所有相關進程會被結束
創建守護進程
1、創建子進程,父進程退出
if (fork() > 0){
exit(0);
}
子進程變成孤兒進程,被init進程收養
子進程在后台運行
2、子進程創建新會話
if (setsid() < 0 ){
exit(-1);
}
子進程成為新的會話組長
子進程脫離原先的終端
3、更改當前工作目錄
chdir("/");
chdir("/tmp");
守護進程一直在后台運行,其工作目錄不能被卸載
重新設定當前工作目錄cwd
4、重設文件權限掩碼
if (umask(0) < 0){
exit(-1);
}
文件權限掩碼設置為0
值影響當前進程
5、關閉打開的文件描述符
int i;
for (i = 0; i < getdtablesize(); i++){
close(i);
}
關閉所有從父進程繼承的打開文件
已脫離終端,stdio,stdout,stderr無法在使用
創建守護進程,每隔1秒將系統時間寫入文件time.log
#include <stdio.h> #include <unistd.h> #include <time.h> #include <stdlib.h> #include <sys/stat.h> // for umask() int main(int argc, char *argv[]) { pid_t pid; FILE *fp; time_t t; int i; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid > 0) { exit(0); } setsid(); umask(0); chdir("/tmp"); for (i = 0; i < getdtablesize(); i++) { close(i); } if ((fp = fopen("time.log", "a")) == NULL) { perror("fopen"); exit(-1); } while (1) { time(&t); fprintf(fp, "%s", ctime(&t)); fflush(fp); sleep(1); } return 0; }