本文章为个人学习笔记,其中可能有些知识点解释的不是那么正确等,所以该笔记仅供参考、借鉴,也可以指点!
什么是程序,什么是进程,有什么区别?
程序是未被 CPU 执行之前,程序做不了任何事,他只是一些代码或者一些文件等。
如前所述,当这个程序被执行的时候,执行他的文件称为进程。
程序是静态的概念,例如gcc xxx.c –o pro 磁盘中生成pro的执行文件,叫做程序
进程是当这个可执行的文件被执行的时候,即可执行的文件或者该程序被cpu拿去执行去了。
eg:在终端执行了 ls -l、ps _aux、./a.out等指令的时候就是打开了相应的进程。
什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID, 叫做pid,类似身份证;
Pid=0: 称为交换进程(swapper) 作用—进程调度 Pid=1:init进程 作用—系统初始化;
编程调用getpid函数获取自身的进程标识符 getppid获取该子进程的父进程的进程标识符(ID)。
什么叫父进程,什么叫子进程
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系;父子进程也有类似嵌套的关系,即A创建了B,那么A是B的父进程;此时B又创建了C,那么B也是C的父进程。
C程序在存储空间的分配
fork 函数
#include <unistd.h>
pid_t fork(void);
创建一个新进程(子进程),且父子进程执行是没有先后顺序,由系统调度的安排;创建的新进程会将父进程原有的储存空间内的数据拷贝一份到新进程当中(fork之前的数据),而形成一个独立的储存空间;在进程创建成功之后,父子进程只修改了自己进程内的数据时,不影响其他进程中数据,因为他们储存空间时独立,的。
fork的返回值,
它可能有三种不同的返回值:
1)在父进程中,fork返回最新创建子进程的进程ID;
2)在子进程中,fork则返回0;
3)如果出现错误,fork返回一个负值;
返回值可参考下面的代码及运行结果
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int a=10;
printf("a=%d\n",a);
printf("fork之前的进程id %d !\n",getpid());
pid=fork();
printf("fork之后的进程id %d !\n\n",getpid());
//while(1)
//{
if(pid>0)
{
a+=10;
printf("这是父进程,a = %d,且fork返回的pid = %d,当前父进程的ID(getpid) = %d !\n",a,pid,getpid());
//sleep(1);
}
else if(pid==0)
{
a+=5;
printf("这是子进程,a = %d,且fork返回的pid = %d,当前子进程的ID(getpid) = %d !\n",a,pid,getpid());
//sleep(1);
}
//}
return 0;
}
运行结果
CLC@Embed_Learn:~/process$ gcc demo2d2.c
CLC@Embed_Learn:~/process$ ./a.out
a=10
fork之前的进程id 9092 !
fork之后的进程id 9092 !
这是父进程,a = 20,且fork返回的pid = 9093,当前父进程的ID(getpid) = 9092 !
fork之后的进程id 9093 !
这是子进程,a = 15,且fork返回的pid = 0,当前子进程的ID(getpid) = 9093 !
CLC@Embed_Learn:~/process$
如果把代码中的while语句加进去的结果如下
a=10
fork之前的进程id 9213 !
fork之后的进程id 9213 !
这是父进程,a = 20,且fork返回的pid = 9214,当前父进程的ID(getpid) = 9213 !
fork之后的进程id 9214 !
这是子进程,a = 15,且fork返回的pid = 0,当前子进程的ID(getpid) = 9214 !
这是父进程,a = 30,且fork返回的pid = 9214,当前父进程的ID(getpid) = 9213 !
这是子进程,a = 20,且fork返回的pid = 0,当前子进程的ID(getpid) = 9214 !
这是子进程,a = 25,且fork返回的pid = 0,当前子进程的ID(getpid) = 9214 !
这是父进程,a = 40,且fork返回的pid = 9214,当前父进程的ID(getpid) = 9213
其中我们可以看出,当fock之后,后面的代码在父子进程中都运行的一次,通过a的值处理发现,父子进程都是独自处理的,并不影响其他进程。且主进程(fork之前)的id和父进程的id一样,在父进程中fork返回的是子进程的ID,而在子进程中fork返回的是0,因此可以通过这种方式判断该进程是子进程还是父进程。
在把while语句加进去发现,父子进程的执行的时候是没有先后顺序的。
vfork
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
也是创建一个新的进程,且这个创建的新进程是先执行子进程,再去执行父进程。且子进程与父进程的数据是共享的,也就是说他们之间是没有独立的储存空间。如果其中一个进程对该共享空间内的数据进行了修改,那么会影响其他进程调用该内存空间的数据内容。
如果用vfork创建了新的进程,且该进程进入了一个循环体当中,那么父进程只有等到子进程将循环体执行完之后在进入父进程,那么父进程就得等待(wait等待函数)子进程达到退出条件(exit等退出函数)后退出后才能执行。
vfork的返回值和fork一样的。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int cent=0;
int num=0;
pid=vfork();
if(pid>0)
{
cent=5;
while(cent)
{
num++;
cent--;
printf("这是父进程,num=%d,vfork返回的ID = %d,当前进程ID = %d !\n",num,pid,getpid());
sleep(1);
}
printf("vfork中的父进程执行了五次之后退出!\n");
}
else if(pid==0)
{
while(1)
{
num++;
printf("这是子进程,num = %d,vfork返回的ID = %d,当前进程ID = %d !\n",num,pid,getpid());
sleep(1);
cent++;
if(cent==3)
{
printf("vfork中的子进程执行了三次之后退出!\n");
exit(0);
}
}
}
return 0;
}
执行结果:
CLC@Embed_Learn:~/process$ gcc demo6d3.c
CLC@Embed_Learn:~/process$ ./a.out
这是子进程,num = 1,vfork返回的ID = 0,当前进程ID = 9843 !
这是子进程,num = 2,vfork返回的ID = 0,当前进程ID = 9843 !
这是子进程,num = 3,vfork返回的ID = 0,当前进程ID = 9843 !
vfork中的子进程执行了三次之后退出!
这是父进程,num=4,vfork返回的ID = 9843,当前进程ID = 9842 !
这是父进程,num=5,vfork返回的ID = 9843,当前进程ID = 9842 !
这是父进程,num=6,vfork返回的ID = 9843,当前进程ID = 9842 !
这是父进程,num=7,vfork返回的ID = 9843,当前进程ID = 9842 !
这是父进程,num=8,vfork返回的ID = 9843,当前进程ID = 9842 !
vfork中的父进程执行了五次之后退出!
CLC@Embed_Learn:~/process$
从运行结果中可以看出,vfork创建的进程是先运行子进程的,且等待子进程运行完了,父进程才执行。且父进程当中的num值是继续子进程的num值继续++的,所以vfork创建的子进程是直接访问父进程的存储空间的。
进程的退出
正常退出
- Main函数调用return
- 进程调用exit(),标准c库
- 进程调用_exit()或者_Exit(),属于系统调用
- (补充:进程最后一个线程返回 最后一个线程调用pthread_exit)
异常退出
- 调用abort
- 当进程收到某些信号时,如ctrl+C
- 最后一个线程对取消(cancellation)请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、- exit和_ Exit), 实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生-一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下, 该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
进程的退出exit与等待wait
exit 退出 他会把进程执行时缓冲区所产生的一些缓存数据处理了才退出。
#include <stdlib.h>
void exit(int status);
wait 进程等待
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);//常用这种
status参数:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空NULL:不关心退出状态
pid_t waitpid(pid_t pid, int *status, int options);
区别:
wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
●如果其所有子进程都还在运行,则阻塞。
●如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
●如果它没有任何子进程,则立即出错返回。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int cent=0;
pid=vfork();
if(pid>0)
{
//wait(NULL);
while(1)
{
printf("这是父进程,pid = %d,当前进程id= %d !\n",pid,getpid());
sleep(1);
}
}
else if(pid==0)
{
while(1)
{
printf("这是子进程,pid = %d,当前进程id = %d !\n",pid,getpid());
sleep(1);
cent++;
if(cent==5)
{
printf("vfork之后,子进程执行五次退出!\n");
exit(0);
}
}
}
return 0;
}
当子进程调用exit退出时,没有父进程调用wait等待,那么运行之后的进程信息如下:
当子进程调用exit退出时,且父进程调用wait等待阻塞,知道收集到子进程的exit状态才执行父进程,那么运行之后的进程信息如下:
等待时,收集子进程exit的状态代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cent=0;
int status=0;
pid=fork();
if(pid>0)
{
wait(&status);
//waitpid(pid,&status,WNOHANG);//这个用法并不多
printf("子进程退出后,收集到exit的状态 status = %d \n",WEXITSTATUS(status));
while(1)
{
printf("这是父进程,pid = %d,当前ID = %d !\n",pid,getpid());
sleep(1);
}
}
else if(pid==0)
{
while(1)
{
printf("这是子进程,pid = %d,当前ID = %d !\n",pid,getpid());
sleep(1);
cent++;
if(cent==3)
{
printf("fork之后,子进程执行了三次后退出!\n");
exit(5);
}
}
}
return 0;
}
wait和waitpid分别运行结果如下:
waitpid
孤儿进程
当父子进程正在运行的时候,父进程突然退出或者是终止运行时;那么,此时的子进程就是没有父进程的进程,但该子进程依旧运行着;当上述情况发生时,子进程就成了孤儿进程。因为Linux系统不允许存在太多的孤立进程。则会创建一个进程ID为1的父进程收留该孤儿进程。