什么是进程:
简单的说,进程就是正在执行的程序。执行程序的时候,内核会为程序分配空间,记录有进程相关的各种信息,并且为进程分配各种计算机资源,在进程结束后,内核会回收资源,供其他进程使用。
进程的内存布局:
文本:程序的指令
数据:程序使用的静态变量
堆:程序可以从这个区域动态分配内存
栈:用于为局部变量和函数调用链接信息分配储存空间,随着函数的调用、返回而增减的内存
创建进程:
用fork函数可以创建新的进程,新进程叫做子进程,原进程叫做父进程,子进程是父进程的copy,包括数据空间,堆和栈的副本,所以父进程和子进程的内存空间不同(但是在内存中标记为只读的内存可以在父子进程之间共享,如果父子进程中任意一个进程试图修改该内存,将会为修改的内存单独创建副本,这个技术叫做写时复制技术Copy-On-Write)
fork函数被调用一次会返回两次,子进程的返回值是0,而父进程的返回值则是子进程的进程编号PID(Procress Identification),每个进程都有一个唯一的PID来标识,此外还有一个父进程来(PPID),用来标识请求内核创建自己的进程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int global_a = 10;
int main()
{
printf("pid = %d,ppid = %d\n",getpid(),getppid());
int local_b = 100;
int *heap_c = (int *)malloc(sizeof(int));
*heap_c = 1000;
pid_t pid = fork();
if(pid == -1)
{
perror("fork failed.");
_exit(errno);
}
if(0 == pid){
printf("this is a child progrocess,pid = %d, ppid = %d\n",getpid(),getppid());
++global_a,--local_b;
*heap_c += 1;
printf("[child]a= %d,b = %d,c = %d\n",global_a,local_b,*heap_c);
_exit(0);
}
else {
sleep(1);
printf("this is parent,pid = %d,child pid = %d\n",getpid(),pid);
printf("[parent]a = %d,b = %d,c = %d\n ",global_a,local_b,*heap_c);
_exit(errno);
}
return 0;
}
输出:
pid = 5038,ppid = 3198
this is a child progrocess,pid = 5039, ppid = 5038
[child]a= 11,b = 99,c = 1001
this is parent,pid = 5038,child pid = 5039
[parent]a = 10,b = 100,c = 1000
父子进程的global_a,local_b,heap_c的取值都不相同,所以他们财产分家了
将sleep(1)注释掉后,发现输出是这样:
this is parent,pid = 5056,child pid = 5057
[parent]a = 10,b = 100,c = 1000
this is a child progrocess,pid = 5057, ppid = 5056
[child]a= 11,b = 99,c = 1001
可见父进程先下手返回了pid(姜还是老的辣)
我们创建进程肯定不是为了去执行父进程那些老套的代码,子进程有自己的想法,于是需要用到exec函数去加载新的程序替换掉老的代码、数据、堆栈。exec总共有6个函数,统称exec.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
void exec_demo()
{
printf("---%s---\n",__FUNCTION__);
/*execl,可变参数列表以NULL结束*/
execl("/bin/ls","/bin/ls","-l","-a",NULL);
printf("execl failed !\n");
}
int main()
{
exec_demo();
printf("My child dont't want me anymore! wuwu...\n ");
return 0;
}
execl()函数的第一个参数是将要执行的新的程序名,可以是加路径,之后的参数是对这个命令的描叙参数,通常第一个是程序名,接下来的是这个命令的可选参数
execl的l就是代表这个参数列表
打印出:
---exec_demo---
总用量 64
drwxrwxr-x 3 jack jack 4096 1月 25 23:21 .
drwxr-xr-x 6 jack jack 4096 1月 25 20:39 ..
-rwxrwxr-x 1 jack jack 8689 1月 25 23:21 1
-rw-rw-r-- 1 jack jack 354 1月 25 23:20 exec.c
-rw------- 1 jack jack 12288 1月 25 23:20 .exec.c.swp
-rw------- 1 jack jack 12288 1月 25 20:07 .fopen.c.swp
-rw-r--r-- 1 jack jack 12288 1月 23 20:51 .main.c.swp
drwxrwxr-x 3 jack jack 4096 1月 23 14:12 .metadata
相当于执行命令ls -l -a,并且看不到printf函数的输出内容,这是因为执行execl函数用新的代码覆盖了旧的代码
当然,exec函数一般都是配合fork函数使用的,先fork一个新进程,再exec去执行新的程序
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/types.h>
6 #include <sys/wait.h>
7
8 int main()
9 {
10 printf("---fork+exec---\n");
11 pid_t pid = fork();
12 if(pid == -1)
13 {
14 perror("fork");
15 _exit(-1);
16 }
17 if(0 == pid){
18 printf("child :pid = %d,ppid = %d\n",getpid(),getppid());
19 execlp("sh","sh","-c","firefox http://baidu.com",NULL);
20 fprintf(stderr,"execlp failed");
21 _exit(-1);
22 }
23 printf("parent,pid = %d,child pid = %d\n",getpid(),pid);
24 return 0;
25
26 }
这里execlo的p代表PATH,表明新的执行程序要到环境变量中一个个去寻找
同样printf的内容不会输出,而是会用firefox打开一个百度网页哦!