• 【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 
    #include 
    //函数接口
    pid_t getpid(void);//查看进程ID
    pid_t getppid(void);//查看父进程ID
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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 
    pid_t fork(void);
    
    • 1
    • 2
    • 3

    image-20221119210139804

    从它的介绍中可以知道fork对父进程返回大于0的数字(本质上是子进程的pid),对子进程返回0,失败返回-1

    #include 
    #include 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这真的是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 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    image-20221119213127851

    开始测试

    image-20221119213148845

    看到结果

    image-20221119213231748


    僵尸进程危害

    • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎
      么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
    • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
      说, Z状态一直不退出, PCB一直都要维护?是的!
    • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构
      对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空
      间!
    • 内存泄漏?是的!
    • 如何避免?后面详细学习

    孤儿进程

    父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

    父进程先退出,子进程就称之为“孤儿进程”

    孤儿进程被1号init进程领养,当然要有init进程回。

    #include 
    #include 
    #include 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20221119213724216

    image-20221119213758194

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


  • 相关阅读:
    如何解决Windows本地微服务并发启动后端口占用问题
    【目标检测】SPP-Net中候选区域在原图和feature map之间的映射关系
    OpenKruise-CloneSet
    【自然语言处理(NLP)】文本数据处理实践
    动态密码 作为程序员之网络安全一定要看
    一文彻底搞懂JavaScript中的prototype
    耿耿为民心
    Ubuntu配置NFS服务器(Linux挂载Linux)
    Selling Partner API Document
    Camunda 动态增加会签
  • 原文地址:https://blog.csdn.net/dongming8886/article/details/127954880