程序:死的,隻占用磁盤空間
進程:活的,運行起來的程序,占用系統資源(CPU,内存等)
單道程序設計:所有進程一個一個執行,A執行完了才能執行B
多道程序設計:進程相互穿插執行,并行執行
虛拟内存與物理内存映射
MMU:内存管理單元,完成虛拟地址到物理地址映射
不同進程用戶空間内存映射到不同物理内存區域,而内核空間内存映射到同一塊物理内存區域,因為操作系統就一個,在這個物理内存區域包括了不同進程的PCB(結構體)。
在用戶空間分配一個數組(虛拟内存空間中數組地址是連續的),如果數組長度很長,在映射到物理内存上時,其在物理内存上的地址其實是不連續的,但不影響,反正我們平常取得地址都是虛拟内存地址。
MMU還可以修改程序訪問内存級别,普通程序訪問級别為0,操作系統為3,執行系統調用時,MMU修改了程序訪問内存的訪問級别,這個速度比較慢。
PCB進程控制塊
進程狀态
進程挂起的時候會釋放CPU,例如sleep函數會挂起進程。
常用的幾個環境變量
PATH:可執行文件搜索路徑
SHELL:當前使用shell類型
TERM:當前終端類型,在圖形界面終端下它的值通常是xterm,終端類型決定了一些程序的輸出顯示方式,比如圖形界面終端可以顯示漢字,而字符終端一般不行。
LANG:語言和字符編碼方式
HOME:家目錄路徑
fork()
pid_t fork(void);
pid_t getpid(void);//獲取進程号
pid_t getppid(void);//獲取父進程号
uid_t getuid(void);//returns the real user ID of the calling process.
uid_t geteuid(void);//returns the effective user ID of the calling process.
gid_t getgid(void);//returns the real group ID of the calling process.
gid_t getegid(void);//returns the effective group ID of the calling process.
成功:父進程返回子進程進程号pid,子進程返回0;
失敗:父進程返回-1,設置errno,不創建子進程;
子進程把父進程的數據複制一份
fork一般判斷方式:
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
}
else if(!pid)
{
//...
}
else
{
//...
}
循環創建多個子進程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/*
int main(int argc, char const *argv[])
{
for(int i=0;i<5;i )
{
if(fork()==0)
{
printf("--I am the %d child process.\n", i 1);
break;
}
}
return 0;
}
*/
int main(int argc, char const *argv[])
{
int i = 0;
for(i=0;i<5;i )
{
if(fork()==0)
{
//printf("--I am the %d child process.\n", i 1);
break;
}
sleep(1);//讓後面打打印依序打印
}
if(i==5)
{
printf("--I am the parent process.\n");
}
else
{
printf("--I am the %d child process.\n", i 1);
}
return 0;
}
父子進程共享哪些内容?
子進程把父進程的數據複制一份,父子進程間遵循讀時共享寫時複制原則。
fork之後先執行父進程還是子進程是不确定的,取決于内核的調度算法。
gdb調試
使用gdb調試時,gdb隻能跟蹤一個進程,可以在fork函數調用之前,通過指令設置gdb調試跟蹤父進程還是子進程,默認跟蹤父進程。
set follow-fork-mode child:設置gdb在fork之後跟蹤子進程
se follow-fork-mode parent:設置gdb在fork之後跟蹤父進程
注意:一定要在fork函數之前設置才有效。
不管跟蹤父進程還是子進程,程序運行結果都是一樣的。隻是單步調試的時候,走的語句不一樣。
exec函數族fork創建子進程後執行的是和父進程相同的程序,子進程往往要調用一種exec函數去執行另一個程序,當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序開始處開始執行,調用exec并不創建新進程,exec前後進程的id不變。
将當前進程的.text,.data替換成要加載的程序的.text,.data。
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
l:表示參數以列表方式提供
v:表示參數以數組方式提供
p:表示在環境變量path路徑下查找可執行文件。
e:使用環境變量數組,不實用進程原有的環境變量,設置新加載程序的環境變量
事實上,隻有execve是真正的系統調用,其他的幾個函數最終都是調用它,execve在man第二節,其他在第三節。
參數:
path:完整可執行程序路徑 文件名
file:在環境變量path路徑下查找可執行程序文件,隻指定文件名即可
arg:傳遞給新進程的參數,必須以NULL結尾,其中arg[0]一般存程序名,
返回值:通常情況下,exec函數不會返回,調用成功,跳轉到新的程序入口處;錯誤時,返回-1,并設置errno。
execlp:通常用來調用系統程序,如ls、data、cp、cat等。其實,execlp也會在當前目錄下查找可執行程序。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
execlp("ls","ls","-l","-a","-h",NULL);
//execlp("ls","ls","-alh",NULL);//這裡這樣寫效果一樣
perror("execlp error");//execlp出錯才返回執行
exit(1);
}
else
{
sleep(1);
printf("--parent process---\n");
}
return 0;
}
execl:一般用于執行自己的程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
execl("./printf_hello","./printf_hello",NULL);//main函數命令行參數
//execlp("./printf_hello","./printf_hello",NULL);//這裡也可以execlp,它也會查找當前路徑
perror("execl error");//execl出錯才返回執行
exit(1);
}
else
{
sleep(1);
printf("--parent process---\n");
}
return 0;
}
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("hello world\n");
return 0;
}
execvp:
char *arg[] = {"ls","-a","-l","-h",NULL};
execvp("ls",arg);
孤兒進程:
父進程先于子進程結束,子進程變為孤兒進程,systemd進程會成為孤兒進程的父進程
ps ajx
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
while(1)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("--parent processs--pid=%d--\n",getpid());
sleep(9);
}
return 0;
}
kill -9 殺掉子進程
僵屍進程:
進程終止,父進程尚未回收。子進程殘留資源(PCB)存放于内核中,變成僵屍(Zombie)進程。
僵屍進程是不能用kill命令清除掉的,因為kill命令是用來終止進程的,而僵屍進程已經終止,可以kill掉僵屍進程父進程,來讓系統進行回收。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(9);
}
else
{
while(1)
{
printf("--parent processs--pid=%d--\n",getpid());
sleep(1);
}
}
return 0;
}
子進程結束前:
子進程結束後:
defunct:死者
wait函數阻塞回收任意一個子進程
一個進程在終止時會關閉掉所有的文件描述符,釋放在用戶空間分配的内存,但它的PCB還保留着,内核在其中保存了一些信息;如果是正常終止,則保存着退出狀态,如果是異常終止,則保存着導緻該進程終止的信号是哪個。這個進程的父進程可以調用wait或者waitpid獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀态可以在shell中用特殊變量$?查看,是因為shell是它的父進程,當它終止時,shell調用wait或者waitpid得到它的退出狀态,同時徹底清除掉這個進程。
父進程調用wait回收子進程終止信息,wait函數包括三個功能:
1. 阻塞等待子進程退出
2. 回收子進程殘留資源
3. 獲取子進程結束狀态(退出原因,正常退出->退出值,異常終止->終止信号)
pid_t wait(int *stat_loc);
stat_loc:傳出參數,保存子進程的退出狀态
成功:返回回收的子進程的pid,
失敗:返回-1
判斷進程退出狀态的幾個宏:
WIFEXITED(stat_val)//判斷進程是否正常終止
WEXITSTATUS(stat_val)//取退出值
WIFSIGNALED(stat_val)//判斷進行是否被信号終止
WTERMSIG(stat_val)//取信号值
WIFSTOPPED(stat_val)
WSTOPSIG(stat_val)
WIFCONTINUED(stat_val)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid_t wpid;
int status;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(10);
return 100;//查看退出狀态是否為100
}
else
{
int wpid = wait(&status);
if(wpid==-1)
{
perror("wait error");
exit(1);
}
printf("--wpid=%d----\n",wpid);
if(WIFEXITED(status))//為真,說明子進程正常終止
{
printf("--child process exit with %d\n--",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))//為真,說明子進程是被信号終止
{
printf("--child process killed with %d\n--",WTERMSIG(status));
}
}
return 0;
}
正常退出:
使用信号殺死:
當父進程不關心子進程的退出狀态時,wpid=wait(NULL);
waitpid函數
pid_t waitpid(pid_t pid, int *stat_loc, int options);
返回值:
>0:表示成功回收的子進程pid
0:函數調用時,options設置為WNOHANG,并且,沒有子進程結束。WNOHANG--wait no hang。hang:挂起
-1:失敗,設置errno。
參數:
options:設置為WNOHANG時,指定回收方式為非阻塞。默認為0,阻塞。
WNOHANG return immediately if no child has exited.
pid:
>0:回收指定id的子進程
-1:回收任意子進程(相當于wait)
0:回收和當前調用watipid在一個組的所有子進程
<-1:回收指定進程組内的任意子進程
stat_loc:子進程退出狀态,和wait函數中參數含義一樣。
waitpid(-1,NULL,0);等價于wait(NULL);
waitpid示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int i = 0;
int pid,pid_waitfor,wpid;
for(i=0;i<5;i )
{
pid = fork();
if(!pid)
{
break;
}
if(i==2)
{
pid_waitfor = pid;//指定回收第三個子進程,父進程中保存第三個子進程pid
}
//sleep(1);
}
if(i==5)
{
wpid = waitpid(pid_waitfor,NULL,0);//阻塞等待回收第三個子進程
//wpid = waitpid(-1,NULL,WNOHANG);//不阻塞回收任意一個子進程,沒有結束的子進程,返回值為0。
//wpid = waitpid(-1,NULL,0);//阻塞回收任意一個子進程。
if(wpid==-1)
{
perror("wait error");
exit(1);
}
printf("--parent process--waitfor %d--\n",wpid);
}
else
{
sleep(1);
printf("--child process--pid=%d--\n", getpid());
}
return 0;
}
wait、waitpid函數一次隻能回收一個子進程,要回收多個子進行需要循環。
循環回收多個子進程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int i = 0;
int pid,wpid;
for(i=0;i<5;i )
{
pid = fork();
if(!pid)
{
break;
}
}
if(i==5)
{
/*
while((wpid=waitpid(-1,NULL,0))!=-1)//注意最後返回-1
{
printf("--wait child--%d\n",wpid);
sleep(1);
}
*/
while((wpid=waitpid(-1,NULL,WNOHANG))!=-1)
{
if(wpid>0)
{
printf("--wait child--%d\n",wpid);
}
else if(wpid==0)
{
sleep(1);
}
}
}
else
{
sleep(1);
printf("--child process--pid=%d--\n", getpid());
}
return 0;
}
發現waitpid沒有子進程可回收後返回-1。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int wpid;
//wpid=waitpid(-1,NULL,0);
wpid=waitpid(-1,NULL,WNOHANG); //兩種結果一樣,不管有沒有設置阻塞,沒有子進程可回收後返回-1
if(wpid==-1)
{
printf("沒有子進程,返回-1\n");
}
if(wpid==0)
{
printf("沒有子進程,返回0\n");
}
return 0;
}
,