文章目录
- 1. 什么是系统调用
- 1.1 通过系统调用获取进程标示符
- 通过其他方式查看PID
- Top
- PS
- 使用PID
- 1.2 通过系统调用创建进程-fork初识
- 2. 进程状态
- 看看Linux内核源代码怎么定义
- 查看状态
- Z(zombie)-僵尸进程
- 僵尸进程危害
- 孤儿进程
1. 什么是系统调用
在linux中,系统调用是指操作系统提供给用户程序调用的一组特殊接口,用户程序可以根据这组接口获得操作系统内核的服务;系统调用规定了用户进程陷入内核的具体位置,或者说规划了用户访问内核的路径,只能从固定位置进入内核。
通俗点讲:在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分
由操作系统提供的接口,叫做系统调用
系统调用类比于银行存钱,到银行存钱对于老百姓来说,是不可以直接跑到银行内部进行操作的,因为银行是有责任保护银行中的一切数据和钱财,所以银行一般都要封闭起来管理,但是又不能完全封闭,因为老百姓也要来办理业务,所以银行最终呈现半封闭的状态,在前台有一小半个小窗口是对老百姓开放的,以方便大家办理业务。
同理操作系统也是如此,我们不能直接对系统做操作,只能通过系统调用来进行访问。
1.1 通过系统调用获取进程标示符
进程id (PID)
首先我们来学习PID这个概念,PID全称Process ID,是标识和区分进程的ID。Linux系统保证不会同时存在两个进程拥有相同的PID,但在一个进程结束之后,其PID可能会再次被分配给新进程
父进程id(PPID)
每个进程除了一定有PID还会有PPID,也就是父进程ID,通过PPID可以找到父进程的信息。
为什么进程都会有父进程ID呢?因为进程都是由父进程衍生出来的
操作系统给我们提供了两个查看pid和ppid的系统接口
//所需的头文件
#include <sys/types.h>
#include <unistd.h>
//函数接口
pid_t getpid(void);//查看进程ID
pid_t getppid(void);//查看父进程ID
运行结果:
通过其他方式查看PID
首先我们想知道进程的PID,可以通过top
或者ps
命令来查看。
Top
在命令行执行top
后,得到类似下面的输出
PS
执行ps axj
后输出如下,其中axj
参数让ps
命令显示更详细的参数信息。
使用PID
拿到PID后,我们就可以通过kill
命令来结束进程了,也可以通过kill -9
或其他数字向进程发送不同的信号。
信号是个很重要的概念,我们后面会详细介绍,那么有了进程ID,我们也可以看看进程名字。
1.2 通过系统调用创建进程-fork初识
- 运行 man fork 认识fork
- 感性的知道fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)(后面章节细讲)
//头文件
#include <unistd.h>
pid_t fork(void);
从它的介绍中可以知道fork对父进程返回大于0的数字(本质上是子进程的pid),对子进程返回0,失败返回-1
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0)
{
perror("fork");
return 1;
}
else if(ret == 0)
{ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else
{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
2. 进程状态
看看Linux内核源代码怎么定义
根据进程的定义,我们知道进程是代码运行的实体,而进程有可能是正在运行的,也可能是已经停止的,这就是进程的状态。
网上有人总结进程一共5种状态,也有总结是8种,究竟应该怎么算呢,最好的方法还是看Linux源码。
下面的状态在kernel源代码里定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
这真的是Linux的源码,可以看出进程一共7种状态,含义也比较清晰,注意其中D(disk sleep)称为不可中断睡眠状态(uninterruptible sleep)。
- R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列
里。- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))。- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的
进程通常会等待IO的结束。- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
以通过发送 SIGCONT 信号让进程继续运行。- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
查看状态
通过ps aux
可以看到进程的状态。
O:进程正在处理器运行,这个状态从来没有见过.
S:休眠状态(sleeping)
R:等待运行(runable)R Running or runnable (on run queue) 进程处于运行或就绪状态
I:空闲状态(idle)
Z:僵尸状态(zombie)
T:T停止状态(stopped)
D: 不可中断的深度睡眠,一般由IO引起,同步IO在做读或写操作时,cpu不能做其它事情,只能等待,这时进程处于这种状态,如果程序采用异步IO,这种状态应该就很少见到了
其中就绪状态表示进程已经分配到除CPU以外的资源,等CPU调度它时就可以马上执行了。运行状态就是正在运行了,获得包括CPU在内的所有资源。等待状态表示因等待某个事件而没有被执行,这时候不耗CPU时间,而这个时间有可能是等待IO、申请不到足够的缓冲区或者在等待信号。
Z(zombie)-僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)
没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
一个创建维持30秒的僵死进程例子 :
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id < 0){
perror("fork");
return 1;
}
else if (id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
编译并在另一个终端下启动监控
开始测试
看到结果
僵尸进程危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎
么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
说, Z状态一直不退出, PCB一直都要维护?是的!- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构
对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空
间!- 内存泄漏?是的!
- 如何避免?后面详细学习
孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id < 0){
perror("fork");
return 1;
}
else if (id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
可以看到父进程退出了,子进程的ppid已经变成了1号进程,此时就是操作系统。