一、进程的位置
首先,Linux下C程序⽣成主要由四个步骤组成:预编译、编译、汇编、链接。当程序执行时,操作系统将可执行程序复制到内存中。程序转为进程通常需要以下步骤:
(1)内核将程序读⼊内存,为程序分配内存空间;
(2)内核为该进程保存PID及相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可被操作系统的调度程序执行了。
进程的内存映像是指内核在内存中如何存放可执行程序文件。在将程序转化为进程的过程中,操作系统将可执行程序从硬盘复制至内存中,其布局如下:
可执行程序与进程内存映像的不同之处在于:
a. 可执行程序位于磁盘中而内存映像位于内存;
b. 可执行程序没有堆栈,因为程序被加载到内在中才会分配堆栈;
c. 可执行程序虽然也有未初始化数据段但它并不被存储在位于硬盘中的可执行文件中;
d. 可执行程序是静态的、不变的,而内在映像随着程序的执行是在动态变化的,比如数据段随着程序的执行要存储新的变量值,栈在函数调用时也是不断在变化中。
二、进程的状态
R (running) //运行状态 表明进程要么是在运行中要么在运队列。
S (sleeping) //浅度睡眠状态
D (disk sleep) //不可中断睡眠状态 此状态只能自己醒过来,在这个状态的进程通常会等待IO的结束。
T (stopped) //停止状态
X (dead) //死亡状态
Z (zombie) //僵尸状态 此进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
孤儿状态:⼀个父进程退出,而它的⼀个或多个子进程还在运行,那么那些⼦进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
孤儿进程验证程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
int main()
{
pid_t pid;
//创建一个进程
pid = fork();
//创建失败
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//子进程
if (pid == 0)
{
printf("I am the child process.\n");
//输出进程ID和父进程ID
printf("pid: %d\tppid:%d\n",getpid(),getppid());
printf("I will sleep five seconds.\n");
//睡眠5s,保证父进程先退出
sleep(5);
printf("pid: %d\tppid:%d\n",getpid(),getppid());
printf("child process is exited.\n");
}
//父进程
else
{
printf("I am father process.\n");
//父进程睡眠1s,保证子进程输出进程id
sleep(1);
printf("father process is exited.\n");
}
return 0;
}
测试结果如下:
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上。因此孤儿进程并不会有什么危害。
僵尸进程验证程序如下:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process.I am exiting.\n");
exit(0);
}
printf("I am father process.I will sleep two seconds\n");
//等待子进程先退出
sleep(2);
//输出进程信息
system("ps -o pid,ppid,state,tty,command");
printf("father process is exiting.\n");
return 0;
}
测试结果如下:
危害
在lunix系统管理中,当用ps命令观察进程的执行状态时,经常看到某些进程的状态栏为defunct,这就是所谓的“僵尸”进程。“僵尸”进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。由于进程表的容量是有限的,所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。
处理方法
1. 将父进程杀死,使僵尸进程成为孤儿进程,让init进程收养,清理僵尸进程.它产生的所有僵尸进程也跟着消失。
2. 子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。http://www.cnblogs.com/Anker/p/3271773.html
三 、进程终止
进程终止的方式有8种,前5种为正常终止,后3种为异常终止:
1 从main函数返回;
2 调用exit函数;
3 调用_exit或_Exit;
4 最后⼀个线程从启动例程返回;
5 最后⼀个线程调用pthread_exit;
6 调用abort函数;
7 接到⼀个信号并终止;
8 最后⼀个线程对取消请求做出响应。
按照ISO C的规定,一个进程可以登记至少32个函数,这些函数将由exit()自动调用。
一个进程可以登记若干个函数,这些函数由exit()自动调用,这些函数被称为终止处理函数,atexit()函数可以登记这些函数。exit()调用终止处理函数的顺序和atexit()登记的顺序相反,如果一个函数被多次登记,也会被多次调用。
atexit函数是一个特殊的函数,它是在正常程序退出时调用的函数,我们把它叫为登记函数
函数原型:int atexit (void (*)(void))
函数名: atexit()
头文件: #include <stdlib.h>
功 能: 注册终止函数(即main执行结束后调用的函数)
用 法: void atexit(void (*func)(void));
注意:exit调用这些注册函数的顺序与它们 登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。
下面我们演示一下atexit()函数的程序示例:
然后我们通过编写makefile来实现编译
运行代码
分析
atexit函数先注册五个fun函数,,然后等待5秒,再打印”finish”(如果main函数中输出部分不加\n,则main函数要输出的内容会先放到标准输出缓冲区中,当main中调用exit函数的时候,会做一些自身清理工作,同时刷新标准输出缓冲区中的内容),当执行到exit(0)时,exit会自动调用这些已注册过的函数,但是由于压栈过程中先入后出的原则,所以先注册的函数最后执行。
在主函数中,是先打印输出语句,再调用fun5函数,再调用fun4函数,依次直到fun1函数,即atexit函数的调用顺序是和登记顺序相反的,当然,一个函数被多次登记,也会被多次调用。