进程和程序的区别
程序是经过编译器编译出来的一组能够实现某种或某些特定功能的指令集,是静态的躺在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