進程和程序的區別
程序是經過編譯器編譯出來的一組能夠實現某種或某些特定功能的指令集,是靜態的躺在flash中
進程是內核通過加載器將程序加載到內存中一次動態執行過程,因此每個進程都有自己的狀態,虛擬地址,並且進程是操作系統分配資源的基本單位。
什麼是進程狀態
新建態:執行某個程序,即創建一個子進程,此時內核只是創建了必要的管理信息,如進程控制塊PCB
就緒態:當內核完成了進程的創建,並且當前系統的性能和內存的容量均允許,此時會添加進就緒鏈表等待CPU執行
運行態:內核調度器輪詢調度就緒鏈表優先級最高的進程,此時該進程處於運行態,注意單核的CPU某個時刻僅有一個進程處於運行態
阻塞態:處於運行態的進程由於調用阻塞API或者等待某個信號,調度器會將此進程休眠,此時該進程處於阻塞態
終止態:終止態是一個進程消亡的狀態,此時會內核或者其父進程會釋放佔用的資源,代表着一個進程的結束
查看進程狀態:
mcchen@mcchen-virtual-machine:~/test/exec$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5517 5516 0 80 0 - 2662 wait pts/0 00:00:01 bash
0 R 1000 6602 5517 0 80 0 - 1177 - pts/0 00:00:00 ps
進程的創建
fork
fork函數時調用一次,返回兩次。在父進程和子進程中各調用一次。子進程中返回值爲0,父進程中返回值爲子進程的PID。程序員可以根據返回值的不同讓父進程和子進程執行不同的代碼,但父進程和子進程的執行順序是不確定的,這個是由內核調度器調度算法決定執行先後。fork創建子進程,把父進程數據空間、堆和棧複製一份,具體代碼片段和執行結果如下
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
void main(void)
{
pid_t pid;
int count = 0;
pid = fork();
if (0 > pid)
{
printf("Cannot fork the new process\n");
return;
}
else if (0 == pid) //子進程
{
count ++;
printf("I am a child process my pid = %d my parent pid = %d count = %d\n", getpid(), getppid(), count);
exit(0);
}
else //父進程
{
count ++;
printf("I am a parent process my pid = %d my child pid = %d count = %d\n", getpid(), pid, count);
wait(NULL);
}
}
root@mcchen-virtual-machine:/home/mcchen/test/fork# gcc -o fork_test fork_test.c
root@mcchen-virtual-machine:/home/mcchen/test/fork# ./fork_test
I am a parent process my pid = 5175 my child pid = 5176 count = 1
I am a child process my pid = 5176 my parent pid = 5175 count = 1
vfork
和fork對比
相同:返回值相同
不同:
fork創建子進程,把父進程數據空間、堆和棧複製一份;
vfork創建子進程,與父進程內存數據共享;
vfork先保證子進程先執行,當子進程調用exit()或者exec後,父進程才往下執行
爲什麼需要vfork?
因爲用vfork時,一般都是緊接着調用exec,所以不會訪問父進程數據空間,也就不需要在把數據複製上花費時間了,因此vfork就是”爲了exec而生“的。具體使用方法見以下代碼片段。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void main(void)
{
pid_t pid;
int count = 10;
pid = vfork();
if (0 > pid)
{
printf("Cannot vfork the new process\n");
return;
}
else if (0 == pid)
{
printf("I am a child process my pid = %d my parent pid = %d\n", getpid(), getppid());
while (5 < count)
{
printf("child count = %d\n", count);
count --;
}
exit(0);
}
else
{
printf("I am a parent process my pid = %d my child pid = %d\n", getpid(), pid);
while (0 < count)
{
printf("parent count = %d\n", count);
count --;
}
return;
}
}
root@mcchen-virtual-machine:/home/mcchen/test/fork# gcc -o vfork vfork.c
root@mcchen-virtual-machine:/home/mcchen/test/fork# ./vfork
I am a child process my pid = 5317 my parent pid = 5316
child count = 10
child count = 9
child count = 8
child count = 7
child count = 6
I am a parent process my pid = 5316 my child pid = 5317
parent count = 5
parent count = 4
parent count = 3
parent count = 2
parent count = 1
root@mcchen-virtual-machine:/home/mcchen/test/fork#
exec族函數
在實際應用中,一般我們會用fork函數創建子進程後,調用exec函數去執行另外一個程序(既可以是二進制文件也可以是linux可執行的腳本文件)。此時子進程完全被替換爲新程序,因爲調用exec函數並不創建新進程,所以前後進程的ID並沒有改變。
函數原型如下
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., 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[]);
返回值:
exec函數族的函數執行成功後不會返回,調用失敗時,會設置errno並返回-1,然後從原程序的調用點接着往下執行。
參數:
path:可執行文件的路徑名字
arg:可執行程序所帶的參數,第一個參數爲可執行文件名字,沒有帶路徑且arg必須以NULL結束
file:如果參數file中包含/,則就將其視爲路徑名,否則就按 PATH環境變量,在它所指定的各目錄中搜尋可執行文件。
exec族函數分類
l : 使用參數列表
p:使用文件名,並從PATH環境進行尋找可執行文件
v:應先構造一個指向各參數的指針數組,然後將該數組的地址作爲這些函數的參數。
e:多了envp[]數組,使用新的環境變量代替調用進程的環境變量
帶l的一類exac函數(l表示list),包括execl、execlp、execle,要求將新程序的每個命令行參數都說明爲 一個單獨的參數。這種參數表以空指針結尾。
下面以execl爲例子說明
/*創建execl.c文件*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void main(void)
{
pid_t pid;
pid = fork();
if (0 > pid)
{
printf("Cannot fork the new process\n");
}
else if (0 == pid)
{
printf("I am a child process my pid = %d my parent pid = %d\n", getpid(), getppid());
printf("before to execl\n");
if (execl("./print_arg", "print_arg", "hello", "world", NULL))
{
printf("execl failed\n");
}
printf("after to execl\n"); //一般execl執行成功是不會執行到這的
}
else
{
printf("I am a parent process my pid = %d my child pid = %d\n", getpid(), pid);
}
return;
}
/*創建print_arg.c文件*/
#include <stdio.h>
void main(int argc, char *argv[])
{
int i;
if (0 >= argc)
{
printf("argc = %d\n", argc);
}
for (i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return;
}
執行結果
mcchen@mcchen-virtual-machine:~/test/exec$ ./execl
I am a parent process my pid = 5926 my child pid = 5927
mcchen@mcchen-virtual-machine:~/test/exec$
I am a child process my pid = 5927 my parent pid = 1
before to execl
argv[0] = print_arg
argv[1] = hello
argv[2] = world
mcchen@mcchen-virtual-machine:~/test/exec$
使用說明:用execl 找到並執行print_arg,將當前子進程替換掉,所以”after to execl” 沒有在終端被打印出來。
帶p的一類exac函數,包括execlp、execvp、execvpe,如果參數file中包含/,則就將其視爲路徑名,否則就按 PATH環境變量,在它所指定的各目錄中搜尋可執行文件。舉個例子,PATH=/bin:/usr/sbin:/sbin
下面以execlp爲例子說明
/*創建execl.c文件*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void main(void)
{
printf("before to execl\n");
if(execl("ps","ps","-l",NULL) == -1) //使用execl函數,由於沒有指定路徑會執行失敗
{
printf("execl failed!\n");
}
printf("after to execl\n");
return;
}
/*創建execlp.c文件*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void main(void)
{
printf("before to execlp\n");
if(execlp("ps","ps","-l",NULL) == -1) //使用execlp函數,雖然沒有指定路徑,但會在環境變量PATCH查找,能夠執行成功
{
printf("execlp failed!\n");
}
printf("after to execlp\n");
return;
}
執行結果與對比
mcchen@mcchen-virtual-machine:~/test/exec$ gcc -o execl execl.c
mcchen@mcchen-virtual-machine:~/test/exec$ gcc -o execlp execlp.c
mcchen@mcchen-virtual-machine:~/test/exec$ ./execl
before to execl
execl failed!
after to execl
mcchen@mcchen-virtual-machine:~/test/exec$ ./execlp
before to execlp
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5517 5516 0 80 0 - 2662 wait pts/0 00:00:01 bash
0 R 1000 6511 5517 0 80 0 - 1177 - pts/0 00:00:00 ps
mcchen@mcchen-virtual-machine:~/test/exec$
帶v不帶l的一類exac函數,包括execv、execvp、execve,應先構造一個指向各參數的指針數組,然後將該數組的地址作爲這些函數的參數。
下面以execvp爲例子說明
/*創建execvp.c文件*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void main(void)
{
char *argv[] = {"ps","-l",NULL};
printf("before to execvp\n");
if(execvp("ps",argv) == -1)
{
printf("execvp failed!\n");
}
printf("after execvp\n");
return;
}
執行結果如下
mcchen@mcchen-virtual-machine:~/test/exec$ gcc -o execvp execvp.c
mcchen@mcchen-virtual-machine:~/test/exec$ ./execvp
before to execvp
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5517 5516 0 80 0 - 2662 wait pts/0 00:00:01 bash
0 R 1000 6597 5517 0 80 0 - 1177 - pts/0 00:00:00 ps
mcchen@mcchen-virtual-machine:~/test/exec$
system方式
system()函數調用“/binsh -c command”執行特定的命令,阻塞當前進程知道command命令執行完畢,system函數的原型如下
#include <stdlib.h>
int system(const char *command);
執行system函數時,會調用fork(),execve(),waitpid()等函數,其中任意一個調用失敗,將導致system函數調用失敗
返回值:
失敗,返回-1
當sh不能執行時,返回127
成功,返回進程狀態值
/*創建system.c文件*/
#include <stdlib.h>
#include <stdio.h>
void main(void)
{
int ret;
printf("系統分配的進程號是:%d\n", getpid());
ret = system("ping www.baidu.com -c 10");
printf("返回值爲:%d\n", ret);
return;
}
執行結果如下
mcchen@mcchen-virtual-machine:~/test/exec$ ./system &
[1] 7021
mcchen@mcchen-virtual-machine:~/test/exec$
系統分配的進程號是:7021
PING www.baidu.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38: icmp_req=1 ttl=56 time=6.24 ms
64 bytes from 14.215.177.38: icmp_req=2 ttl=56 time=6.68 ms
mcchen@mcchen-virtual-machine:~/test/exec$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 5517 5516 0 80 0 - 2662 wait pts/0 00:00:01 bash
0 S 1000 7027 5517 0 80 0 - 503 wait pts/0 00:00:00 system
0 S 1000 7028 7027 0 80 0 - 560 wait pts/0 00:00:00 sh
4 S 1000 7029 7028 0 80 0 - 615 unix_s pts/0 00:00:00 ping
0 R 1000 7031 5517 0 80 0 - 1177 - pts/0 00:00:00 ps
mcchen@mcchen-virtual-machine:~/test/exec$
64 bytes from 14.215.177.38: icmp_req=3 ttl=56 time=8.60 ms
64 bytes from 14.215.177.38: icmp_req=4 ttl=56 time=7.23 ms