【Linux】初识系统调用&&进程状态


在这里插入图片描述

1. 什么是系统调用

在linux中,系统调用是指操作系统提供给用户程序调用的一组特殊接口,用户程序可以根据这组接口获得操作系统内核的服务;系统调用规定了用户进程陷入内核的具体位置,或者说规划了用户访问内核的路径,只能从固定位置进入内核。

通俗点讲:在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分
由操作系统提供的接口,叫做系统调用

image-20221119202338323

系统调用类比于银行存钱,到银行存钱对于老百姓来说,是不可以直接跑到银行内部进行操作的,因为银行是有责任保护银行中的一切数据和钱财,所以银行一般都要封闭起来管理,但是又不能完全封闭,因为老百姓也要来办理业务,所以银行最终呈现半封闭的状态,在前台有一小半个小窗口是对老百姓开放的,以方便大家办理业务。

同理操作系统也是如此,我们不能直接对系统做操作,只能通过系统调用来进行访问。


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

image-20221119204400248

运行结果:

image-20221119205305859


通过其他方式查看PID

首先我们想知道进程的PID,可以通过top或者ps命令来查看。

Top

在命令行执行top后,得到类似下面的输出

image-20221119205436415

PS

执行ps axj后输出如下,其中axj参数让ps命令显示更详细的参数信息。

image-20221119205532818

使用PID

拿到PID后,我们就可以通过kill命令来结束进程了,也可以通过kill -9或其他数字向进程发送不同的信号。

信号是个很重要的概念,我们后面会详细介绍,那么有了进程ID,我们也可以看看进程名字。


1.2 通过系统调用创建进程-fork初识

  • 运行 man fork 认识fork
  • 感性的知道fork有两个返回值
  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)(后面章节细讲)
//头文件
#include <unistd.h>
pid_t fork(void);

image-20221119210139804

从它的介绍中可以知道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;
}
image-20221119211321507

image-20221119211338326


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;
}

编译并在另一个终端下启动监控

image-20221119213127851

开始测试

image-20221119213148845

看到结果

image-20221119213231748


僵尸进程危害

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎
    么样了。可父进程如果一直不读取,那子进程就一直处于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;
}

image-20221119213724216

image-20221119213758194

可以看到父进程退出了,子进程的ppid已经变成了1号进程,此时就是操作系统。


猜你喜欢

转载自blog.csdn.net/dongming8886/article/details/127954880
今日推荐