一、进程相关概念
问1:什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c -o pro
磁盘中生成的pro文件,叫做程序
进程是程序的一次运行活动,通俗点的意思就是程序跑起来了,系统中就多了一个进程
问2:如何查看系统中有哪些进程?
- 使用ps指令查看 (ps -aux 所有的)
- 实际工作中,配合grep来查找程序中是否存在某一个进程。如:查找init进程。ps -aux|grep init
- 使用top指令查看,类似于windows任务管理器。
问3:什么是进程标识符?
每个进程都有一个非负整数表示唯一的ID,叫做pid,类似身份证
pid=0; 成为交换进程(sweapper) 作用:进程调度
pid=1;init进程 作用:系统初始化
编程调用getpid函数获取自身的进程标识符 getppid获取父进程的进程标识符
问4:什么叫父进程,什么叫子进程
进程A创建了进程B , 那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系
问5:C程序的储存空间是如何分配?
进程环境之C程序的存储空间布局
从历史上讲,C程序一直由下面几部分组成:
正文段。
这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的指令。
初始化数据段。
(所有带有初始值的全局变量)通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中出现在任何函数之外的声明:
int maxcount = 99;
使此变量带有其初值存放在初始化数据段中。
非初始化数据段。
(所有未带初始值的全局变量)通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明:
long sum[1000];
使此变量存放在非初始化数据段中。
栈。
(局部变量和形参)自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动变量和临时变量分配存储空间。通过这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。
堆。
通常在堆中进行动 态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。
二、创建进程函数fork的使用
使用fork函数创建一个进程
#include <unistd.h>
pid_t fork(void);
fork函数调用成功,返回两次
返回值为0,代表当前进程是子进程
返回值为非负数,代表当前进程为父进程
返回值为 -1,创建子进程失败。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
fork();
printf("pid = %d\n",pid);
return 0;
}
打印出了两遍 pid 说明,有了两个进程!
查看父进程/子进程代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid1,pid2;
pid1 = getpid();
printf("before fork: pid = %d\n",pid1);
fork();
pid2 = getpid();
printf("after fork: pid = %d\n",pid2);
pid2 = getpid();
if(pid1 != pid2)
{
printf("after fork,this is child process pid = %d\n",pid2);
}else{
printf("after fork,this is parent process pid = %d\n",pid2);
}
return 0;
}
用返回值来判断父/子进程代码(1):
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("before fork,pid = %d\n",getpid());
pid = fork();
if(pid > 0)
{
printf("this is parent process,pid = %d\n",getpid());
}else if(pid == 0)
{
printf("this is child process,pid = %d\n",getpid());
}else{
printf("fork error\n");
}
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t retpid;
printf("before fork,pid = %d\n",getpid());
retpid = fork();
if(retpid > 0)
{
printf("this is parent process,pid = %d,retpid = %d\n",getpid(),retpid);
}else if(retpid == 0)
{
printf("this is child process,pid = %d,retpid = %d\n",getpid(),retpid);
}else{
printf("fork error\n");
}
return 0;
}
fork前pid是18576,fork后父进程pid是18576,fork()的返回值大于零且为18577(子进程号)。
fork后子进程pid是18577,fork()的返回值是0.
三、进程创建后 发生了什么事?
fork后父子进程的资源关系: 当使用fork函数时,有的人会注意到一个问题——当在fork函数前定义的变量,被父和子进程调用,且变量的值被父或者子进程改变。 那么变量值在改变之后,是否会使 子 或者 父 进程调用值出现变化?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t retpid;
int data = 666;
retpid = fork();
if(retpid > 0)
{
data = 100;
printf("this is parent process,data's address: %p,data = %d\n",&data,data);
wait(NULL);
}else if(retpid == 0)
{
printf("this is child process,data's address: %p,data = %d\n",&data,data);
}else{
printf("fork error\n");
}
return 0;
}
可见父进程改变 变量data的值,不会影响子进程中a的值。通过打印地址,可以发现a的地址相同,说明两个进程中的a是同一个变量。
但其实那是虚拟地址 映射在不同的物理地址上。实际物理地址还是不同。 对于父子进程,它们的逻辑空间一样,但是物理空间还是不同的。所以在多进程编程中,不要寄希望于通过地址来判断两个变量是否相同。
fork之后,子进程会拷贝父进程的数据空间、堆和栈空间(实际上是采用写时复制技术),二者共享代码段。
子进程相当于一份复制品。父子进程的以下属性在创建之初完全一样:
1)实际UID和GID,以及有效GID和UID
2)所有环境变量
3)进程组ID和会话ID
4)当前工作路径
5)打开的文件
6)信号响应函数
7)整个内存空间,包括栈、堆、代码段、数据段、标准I/O的缓冲区等。
结论:父子进程是相互独立的。由于子进程完整的复制了父进程的内存空间,因此从内存空间的角度看他们是互不影响的。
四、创建新进程的实际应用场景
1. fork 创建子进程的一般目的:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int creat;
pid_t retpid;
while(1)
{
printf("pid = %d,if creat process,please input:\n",getpid());
scanf("%d",&creat);
if(1 == creat)
{
retpid = fork();
if(0 == retpid)
{
printf("pid= %d child process:do net quest\n",getpid());
}else i