Linux系统编程-进程及相关指令与函数

目录

[一. 进程](#一. 进程)

[1.1 什么是进程](#1.1 什么是进程)

进程:

程序:

[1.2 进程的创建过程](#1.2 进程的创建过程)

[1.3 进程的调度](#1.3 进程的调度)

并发:

CPU任务调度算法:

[1.4 进程的状态](#1.4 进程的状态)

三态图

Linux进程状态:

[二. 进程相关的指令](#二. 进程相关的指令)

[2.1 ps aux](#2.1 ps aux)

[2.2 ps -ef](#2.2 ps -ef)

[2.3 pstree](#2.3 pstree)

[2.4 kill](#2.4 kill)

[2.5 fg](#2.5 fg)

[三. 进程控制函数](#三. 进程控制函数)

[3.1 fork](#3.1 fork)

[3.2 getpid与getppid](#3.2 getpid与getppid)

[3.3 孤儿进程](#3.3 孤儿进程)

[3.4 僵尸进程](#3.4 僵尸进程)

[四. 进程的退出与消亡](#四. 进程的退出与消亡)

[4.1 进程的退出](#4.1 进程的退出)

[4.2 如何避免僵尸进程:](#4.2 如何避免僵尸进程:)

一. 进程

单任务:程序运行起来,只能处理一件事情

多任务:程序运行起来,可以处理多件事情

1.1 什么是进程

进程:

**进程:**进程就是运行起来的程序,需要分配CPU资源与内存,多进程之间有资源的竞争

进程是一个动态的过程,具有生命周期

进程可以实现并发 (多任务)

进程是操作系统分配资源的最小单位

程序:

**程序:**存放在硬盘的数据指令集合

静态的

程序运行起来可以创建多个进程

1.2 进程的创建过程

在创建进程时,操作系统会为其分配0~4G的虚拟内存地址空间

1.3 进程的调度

总结一句话:宏观并行,微观串行

宏观上多个任务同时进行,微观上多个任务按照一定的顺序执行

并发:

**并发:**在操作系统中,一个时间段内,有多个进程处于已启动运行到运行结束状态之间,但是同一时间点上,只有一个进程在运行

CPU任务调度算法:

1、时间片轮询

2、先来先服务,后来后服务

3、优先级高者先服务,优先级低者后服务

4、短作业优先

1.4 进程的状态

任何操作系统的进程切换图:

三态图

Linux操作系统的进程切换图:

Linux进程状态:

|--------------------|-------------------------------|
| 运行态(用户运行态,内核运行态) R | 正在被CPU任务调度所执行的进程 |
| 就绪态R | 正在执行,但是没有被CPU任务调度的进程 |
| 可唤醒等待态 S | 也称为睡眠态,阻塞等待资源的进程 |
| 不可唤醒等待态 D | 不想被CPU任务调度打断的进程任务,可设置不可唤醒等待状态 |
| 暂停态 T | 被暂停执行过程 |
| 僵尸态 Z | 进程执行结束,资源没有被回收 |
| 结束态 X | 进程执行结束,空间被回收 |

二. 进程相关的指令

2.1 ps aux

作用:查看操作系统中进程相关的信息

注意:1号进程为init进程,作用:是系统的起点,收养孤儿进程,清理僵尸进程等

|------------|-------------------|
| USER | 用户 |
| PID | 进程ID |
| %CPU %MEM | 进程对CPU,内存的占有率 |
| VSZ RSS | 内存相关信息 |
| TTY | 对终端的使用(?表示没有使用终端) |
| STAT | 进程状态 |
| START TIME | 进程开始的日期和时间 |
| COMMAND | 进程名称 |

2.2 ps -ef

作用:查看进程信息,可以查看进程ID与其父进程ID

2.3 pstree

作用:查看进程间的创建关系 加上选项 -p 可以显示进程ID

2.4 kill

作用:给指定PID的进程发送指定的信号:kill -信号编号/信号名称 PID

信号:可以使用命令 kill -l来查看信号的种类及其信号编号

9号SIGKILL:结束指定的进程

19号SIGSTOP:暂停指定的进程

18号SIGCONT:继续指定的进程

2.5 fg

作用:将指定的进程由后台调至前台

使用方法:fg 进程编号

获取进程编号:指令jobs,作用:获取当前终端的后台进程及其编号

三. 进程控制函数

3.1 fork

关键在于duplicating

**作用:**创建一个子进程

返回值:>0:表示在父进程中返回子进程的PID

=0:表示当前处于子进程中

<0:出错并且设置errno

**注意:**1、子进程完全拷贝父进程的0~3G进程地址空间(用户空间:堆区,栈区,文本区,数据区

等)

2、但是对内核区的拷贝不完全,比如PCB中的PID号就不一样,但是又拷贝了文件描述符

3、父子进程之间数据不共享,要想通信就得使用进程间通信方式

可以使用下面一段代码来验证:

cpp 复制代码
//int num = 100;全局变量不共享
int main()
{
    //int num = 300;//局部变量也不共享
    int *pnum = malloc(sizeof(int));//堆区资源也不共享
    if(pnum == NULL){
        perror("malloc()");
        return -1;
    }
    *pnum = 600;

    pid_t pid = fork();
    if(pid < 0){
        perror("fork()");
        return -1;
    }
    else if(pid > 0){
        while(1){
            printf("---parent: my pid is %d,my parent pid is %d, num = %d\n",
                   getpid(), getppid(), *pnum);
            sleep(1);
        }
    }
    else{
        //num = 200;
        *pnum = 900;
        while(1){
            printf("-----child: my pid is %d,my parent pid is %d, num = %d\n",
                   getpid(), getppid(), *pnum);
            sleep(1);
        }
    }
    return 0;
}

这里有一个小栗子:

问下面这个程序总共可以fork出几个进程?

cpp 复制代码
int main()
{
    fork() && fork() || fork();
    getchar();
    return 0;
}

结果:使用 pstree -sp 进程ID 查看:可以看出总共有五个进程

这里需要注意 &&|| 操作符的短路效应

表达式1 && 表达式2 || 表达式3 这条语句如果表达式1是假,那么后面是不是就都不执行了?不是这样的:表达式1为假,只能短路掉表达式2,|| 左边整体为假,故表达式3仍要执行。

3.2 getpid与getppid

作用:获取当前进程的pid当前进程的父进程的pid

3.3 孤儿进程

父进程先于子进程结束,则此子进程成为孤儿进程,则这个子进程的父进程变成了一个系统进程,在早期的操作系统中,孤儿进程直接被1号进程init进程收养,但是init进程的任务繁重,后来又重新封装了一个进程组来回收孤儿进程

孤儿进程的用处:后台的守护进程

可以使用下面的代码来观察孤儿进程:

cpp 复制代码
int main()
{
    pid_t pid = fork();
    if(pid > 0){
        printf("parent:pid is %d, child is %d\n",getpid(),pid);
        sleep(10);//父进程10s后死亡,子进程成为孤儿进程
    }
    else if(pid == 0){
        while(1){//子进程循环打印 
            printf("child:pid is %d, parent is %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

结果:在父进程结束后,子进程的父进程成为了1495这个进程

3.4 僵尸进程

子进程结束后,父进程没有回收子进程残留在内核空间中的资源(PCB),导致该子进程变成了僵尸进程。

注:子进程结束后,其用户空间 的所有资源默认由操作系统回收,但是内核空间的PCB还残留在内核中,原因:PCB记录了子进程退出的原因,若是正常退出则保存其退出状态,若是异常退出,则保存使其异常退出的原因,比如信号等等,父进程可以调用wait或者waitpid来获取这些信息,然后彻底清除掉这个进程残留的资源。

使用kill -9 命令无法使僵尸进程结束,只能将其父进程结束,然后该子进程成为孤儿进程,然后被收养和回收

使用这段代码可以观察到僵尸进程这个状态:

cpp 复制代码
int main()
{
    pid_t pid = fork();
    if(pid > 0){
        while(1){
            printf("parent:pid is %d, child is %d\n",getpid(),pid);
            sleep(1);//父进程一直在打印,没有回收子进程,则子进程变成僵尸进程
        }
    }
    else if(pid == 0){ 
            printf("child:pid is %d, parent is %d\n",getpid(),getppid());
            sleep(10);//子进程10s后结束
            printf("child die\n");
    }
    return 0;
}

结果:

10s前:

10s后:可以看到子进程变成了僵尸进程Z+

四. 进程的退出与消亡

4.1 进程的退出

  1. 在主函数中return

  2. exit() ,库函数,直接结束一个进程,结束前会刷新缓冲区

  3. _exit() , _Exit(),系统调用,直接结束一个进程,结束前不会刷新缓冲

4.2 如何避免僵尸进程:

  1. 子进程结束,由其父进程回收资源空间

  2. 让子进程成为一个孤儿进程

相关推荐
奋斗的好青年2 小时前
Ubuntu 修复 GRUB 引导并找回 Windows 双系统启动项(NVMe + MBR 环境)
linux·windows·ubuntu
开发者联盟league2 小时前
在ubuntu上安装harbor
linux·运维·ubuntu
ulias2123 小时前
深挖进程间通信的奥秘
java·linux·服务器·开发语言·c++·算法
AOwhisky3 小时前
MySQL 学习笔记(第三期):SQL 语言之数据操作与单表查询
linux·运维·笔记·sql·学习·mysql·云计算
键盘上的猫头鹰4 小时前
【Linux 基础教程(四)】文件内容查看、打包压缩与搜索、重定向管道及环境变量
linux·服务器·python
tellmewhoisi4 小时前
linux 基础知识(文件权限相关)
linux·运维·服务器
jiayong234 小时前
Claude Code 常见操作实战指南
linux·服务器·网络·ai·claude·claude code
醇氧4 小时前
【Linux 】sudo、sudo -i、su、su - 完整区别总结
linux·运维·服务器
xiaoye-duck5 小时前
《Linux系统编程》System V IPC 进阶:消息队列与信号量从入门到内核
linux