Linux系统编程之进程控制(上)

news2024/9/28 9:23:20

一、进程标识

1.pid

        每个进程都有非负整数表示的唯一进程ID,即pid,其类型为pid_t类型。可用ps命令查看当前所有进程的信息,该命令可以加选项,一般使用ps -ef或ps axf(打印进程树),查看当前系统所有进程的信息。需要注意的是和fd(文件描述符)不同,pid是顺次使用的,即当10001,10002,10003被用过后,10002释放了,下个进程的pid会继续使用10004而不是回头使用最小的可用pid10002。

        pid为1的进程是init进程,它是所有进程的祖先进程。 

 2.getpid(),getppid()

        getpid()用于获取当前进程的进程号,而getppid()用于获取当前进程的父进程的进程号。

         返回值为pid_t类型的进程号。

二、进程的产生

1.fork()

        fork()用于产生一个子进程,这个子进程是从父进程拷贝过来的,因此除了以下这几点其他都是一摸一样的,连产生那一刻执行到的位置都是一样的,即子进程会从创建它的那一行代码开始继续往下执行。

不同点:

        1)返回值不一样

        2)pid和ppid不一样

        3)未决信号和文件锁不一样

        4)资源利用量清零

         如果成功,父进程中的返回值是子进程的pid号,子进程中的返回值是0.如果失败,父进程中的返回值是-1.

        另外,父子进程是写时拷贝的,当他们都是只读的时候他们会共享物理内存页,当谁要修改内容时谁就复制一份自己的新的物理内存页,去修改自己的内容。子进程中的地址是虚拟地址,指针变量的值和父进程一样,但指向的实际物理内存是独立的。

fork使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc,char *argv[])
{
    printf("[%d]:Begin\n",getpid());

    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }

    if(pid == 0)
    {
        printf("[%d]:Child is working\n",getpid());
    }
    else
    {
        printf("[%d]:Parent is working\n",getpid());
    }

    printf("[%d]:End\n",getpid());
    exit(0);
}

执行结果:

        父子进程结束顺序是不确定的,由调度器的调度机制决定。

 需要特别注意的是:如果fork之前没有刷新缓冲区,fork之后可能会把之前的内容重复输出,从而导致错误,因此fork之前一定要刷新缓冲区。而且有些内容比如输出到文件的内容是全缓冲的,这时用\n来刷新是无效的,必须要在fork()前用fflush()来刷新缓冲区

将输出重定向到文件中:

 可以看到Begin被输出了两次。

使用fflush():

 

 正确输出。

2.父子进程的关系

        父子进程结束时机不同会产生不同影响。

1)子进程先结束

        如果子进程先于父进程结束,那么子进程会成为一个僵尸进程(zombie process)。僵尸进程是指子进程已经终止,但其父进程尚未通过wait()或waitpid()等系统调用来获取子进程的终止状态。

        当子进程结束时,它的终止状态(退出码)会被保存在操作系统的进程表中,同时进程表中的相关资源也会被释放。但是,子进程的进程表条目仍然存在,以便父进程可以通过wait()或waitpid()等系统调用来获取子进程的终止状态。

        在子进程成为僵尸进程后,它的状态会被标记为"Z"(Zombie)或"defunct"。僵尸进程不会再执行任何代码,也不会占用系统资源,但它的进程表条目仍然存在。

        父进程可以通过调用wait()或waitpid()等系统调用来等待子进程的终止,并获取其终止状态。当父进程获取到子进程的终止状态后,操作系统会将僵尸进程的进程表条目从进程表中删除,释放相关资源。

        需要注意的是,如果父进程没有及时调用wait()或waitpid()等系统调用来处理僵尸进程,那么系统中可能会存在大量的僵尸进程,这可能会导致系统资源的浪费。因此,父进程应该及时处理僵尸进程,以避免这种情况的发生。

2)父进程先结束

        当父进程先于子进程结束时,子进程会成为一个孤儿进程(orphan process)。孤儿进程是指其父进程已经终止的进程。在这种情况下,子进程的新的父进程会被设置为init进程。

        孤儿进程的状态取决于其具体的执行情况。如果孤儿进程仍在执行,则它将继续运行直到完成或被终止。如果孤儿进程在执行期间需要等待某些事件(如I/O操作),则它会被挂起,直到事件发生或被终止。

        一旦孤儿进程终止,它的资源将被操作系统回收。这包括释放内存、关闭打开的文件描述符等。孤儿进程的终止状态(退出码)将被保存,以便父进程可以通过wait()或waitpid()等系统调用来获取。

        需要注意的是,孤儿进程的终止状态不会被发送给其父进程。父进程无法通过常规的方式获取孤儿进程的终止状态。如果父进程希望获取孤儿进程的终止状态,可以使用wait()或waitpid()等系统调用来等待孤儿进程的终止,并获取其终止状态。

三、进程的回收

1.wait(),waitpid()

        wait()和waitpid()都是用来回收子进程的资源的,他们会等待子进程状态变化并获取其状态信息。

         wait()会阻塞等待直到任意一个子进程状态改变,而waitpid可以指定某个子进程,并且可以设置为非阻塞。waitpid()的第一个参数pid和第三个参数options有如下取值,其中options可以用按位或( | )组合使用。

        可以看出,waitpid(-1,&status,0)和wait(&status)是等效的。

        status参数是用来保存子进程状态的,有如下的宏定义。

        二者的返回值差不多,成功返回pid,失败返回-1,不同的是如果waitpid()的options参数设置了WNOHANG(不阻塞),并且调用时没有待回收的子进程,则会返回0.

四、exec函数簇

        我们知道,在shell下执行的可执行文件其父进程时shell,但fork()只能产生和自身一模一样的进程,而我们的可执行文件并不和shell一样,这是怎么实现的呢?

        其实shell中执行文件产生的进程是通过exec函数簇实现的。exec函数簇可以用新的进程映像(process image)代替旧的进程映像,并在新的进程中从头执行。

        换句话说,exec函数簇会将当前进程替换为新的程序,新程序会从头开始执行。它会重新加载新程序的代码段、数据段和堆栈,并开始执行新程序的入口函数。

        特别注意的是,和fork()一样,也要注意缓冲区的刷新问题,所以调用exec函数簇的函数前要用fflush()刷新缓冲区。

1.execl(),execlp(),execle()

1)execl()接受一个文件路径 path 和一系列的参数,以 NULL 结尾。参数 arg0 是新程序的名称,它会作为 argv[0] 传递给新程序,后面的参数是传递给新程序的命令行参数,最后一个参数必须是 NULL。例如:

execl("/bin/ls", "ls", "-l", NULL);

2)execlp()与execl()类似,但是它会在系统的 PATH 环境变量中搜索指定的可执行文件,并执行找到的第一个匹配的文件。例如:

execlp("ls", "ls", "-l", NULL);

3)execle()与execl()类似,但是它还接受一个环境变量数组 envp,用于设置新程序的环境变量。此 envp 是新进程的所有环境变量,其他未设置的环境变量不会继承。环境变量数组的最后一个元素必须是 NULL。例如:

char *env[] = {"PATH=/usr/bin", "HOME=/home/user", NULL};
execle("/bin/ls", "ls", "-l", NULL, env);

        这些函数如果成功都没有返回值,否则返回-1. 

2.execv(),execvp(),execvpe()

 1)execv()接受一个文件路径 path 和一个参数数组 argv,其中 argv[0] 是新程序的名称,后面的元素是传递给新程序的命令行参数,最后一个元素必须是 NULL。例如:

char *args[] = {"ls", "-l", NULL};
execv("/bin/ls", args);

2)execvp()与execv()类似,但是它会在系统的 PATH 环境变量中搜索指定的可执行文件,并执行找到的第一个匹配的文件。例如:

char *args[] = {"ls", "-l", NULL};
execvp("ls", args);

3)execvpe()与execvp()类似,但是它还接受一个环境变量数组 envp,用于设置新程序的环境变量。此 envp 是新进程的所有环境变量,其他未设置的环境变量不会继承。环境变量数组的最后一个元素必须是 NULL。例如:

char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/usr/bin", "HOME=/home/user", NULL};
execvpe("ls", args, env);

        这些函数如果成功都没有返回值,否则返回-1. 

 以下是一个使用示例:

int main()
{
    puts("Begin");
    fflush(NULL);
    
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        exit(1);
    }
    if(pid == 0)
    {
        execl("bin/date","date","+%s",NULL);
        perror("execl()");        //走到这说明execl失败了
        exit(1);
    }

    wait(NULL);

    puts("End");
    exit(0);
}

 运行结果:

五、mysh的实现

        了解了上面的函数,我们就可以自己实现一个简单的shell了,shell可以执行内部命令和外部命令,内部命令我们目前知识还不够,所以当前只需实现可以执行外部命令的shell.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_ARGC 20

char **parse(char *line)
{
    char **args = (char**)malloc(MAX_ARGC * sizeof(char*));
    char *saveptr;
    char *arg;
    args[0] = strtok(line," ");
    int i = 1;

    while((arg = strtok(NULL," ")) != NULL)
    {
        args[i] = arg;
        i++;
    }

    args[i] = NULL;

    return args;
}

int main(int argc,char **argv)
{
    while(1)
    {
        printf("[user@myshell]");
        char *line = NULL;
        size_t n = 0;
        getline(&line,&n,stdin);
        line[strlen(line)-1] = '\0';        //把\n去掉,否则会影响命令行参数
        char **args = parse(line);

        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork():");
        }

        if(pid == 0)
        {
            execvp(args[0],args);

            perror("exec:");
            exit(1);
        }
        else
        {
            free(line);
            free(args);
            wait(NULL);
        }
    }

    return 0;
}

        可以执行ls,du,vim等外部命令。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Rust教程 | 基础系列 | Rust初相识】Rust简介与环境配置

教程目录 前言一&#xff0c;Rust简介1&#xff0c;Rust的历史2&#xff0c;Rust的特性3&#xff0c;为什么选择Rust 二&#xff0c; Rust环境配置1&#xff0c;windows11安装2&#xff0c;Linux安装 三&#xff0c;安装IDE 前言 Rust是一种系统编程语言&#xff0c;专注于速度…

【深度学习】以图搜索- 2021sota repVgg来抽取向量 + facebook的faiss的做特征检索, 从环境搭建到运行案例从0到1

文章目录 前言安装小试牛刀用repVgg抽取向量构建Faiss索引进行相似性搜索项目延伸总结 前言 Faiss的全称是Facebook AI Similarity Search。 这是一个开源库&#xff0c;针对高维空间中的海量数据&#xff0c;提供了高效且可靠的检索方法。 暴力检索耗时巨大&#xff0c;对于…

Flowable-任务-用户任务

定义 顾名思义&#xff0c;用户任务是需要人工参与处理的。当流程执行到用户任务节点时&#xff0c;流程引擎会给指指定的用户&#xff08;办理人或候选人&#xff09;或一组用户&#xff08;候选组&#xff09;创建待处理的任务项&#xff0c;等待用户的处理。 用户任务的参与…

HTML一些基础知识

1、Web标准&#xff1a;主要包含结构、表现、行为。结构用于对网页元素进行整理和分类&#xff0c;主要指HTML。表现用于设置网页元素的板式、颜色、大小等外观样式&#xff0c;主要指的是CSS。行为主要指的是网页模型的定义以及交互的编写&#xff0c;主要是js文件。 Html相当…

AddForce

ForceMode&#xff1a; Force&#xff1a;关注的是力整体 Impulse&#xff1a;关注的是冲量&#xff0c;与质量相关 VelocityChange&#xff1a;关注的是速度&#xff0c;与质量无关 Acceleration&#xff1a;关注的是加速度&#xff0c;与质量无关 public void AddForce…

前后端分离实现博客系统

文章目录 博客系统前言1. 前端1.1 登陆页面1.2 博客列表页面1.3 博客详情页面1.4 博客编辑页面 2. 后端2.1 项目部署2.1.1 创建maven项目2.1.2 引入依赖2.1.3 创建目录结构2.1.4 部署程序 2.2 逻辑设计2.2.1 数据库设计2.2.2 实体类设计2.2.3 Dao层设计2.2.3.1 BlogDao 2.2.4 D…

Intel RealSense D455(D400系列) Linux-ROS 安装配置(亲测可用)

硬件&#xff1a;Intel RealSense D455 系统&#xff1a;Ubuntu 18.04 Part_1: 安装librealsense SDK2.0 1.1 注册密钥 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE或者 sudo apt-key adv --keyserver hkp:/…

Mysql定时删除表数据

由于用户环境有张日志表每天程序都在狂插数据&#xff0c;导致不到一个月时间&#xff0c;这张日志表就高达200多万条记录&#xff0c;但是日志刷新较快&#xff0c;里面很多日志没什么作用&#xff0c;就写了个定时器&#xff0c;定期删除这张表的数据。 首先查看mysql是否开启…

【Linux】进程间通信——system V共享内存 | 消息队列 | 信号量

文章目录 一、system V共享内存1. 共享内存的原理2. 共享内存相关函数3. 共享内存实现通信4. 共享内存的特点 二、system V消息队列&#xff08;了解&#xff09;三、system V信号量&#xff08;信号量&#xff09; 一、system V共享内存 1. 共享内存的原理 共享内存是一种在…

自动化测试Junit(测试系列8)

目录 前言&#xff1a; 1.什么是Junit 2.Junit相关的技术 2.1注解 2.1.1Test 2.1.2Disable 2.1.3BeforeAll和AfterAll 2.1.4BeforeEach和AfterEach 2.2参数化 2.2.1单参数 2.2.2多参数 2.2.2.1CSV获取参数 2.2.2.2方法获取参数 2.3测试套件 2.3.1通过class运行测…

Java工程师研学之路【002Java基础语法上】

知识体系&#xff08;Knowledge system&#xff09; 练习&#xff08;practice&#xff09; 要求&#xff1a;从控制台输入两个数字&#xff0c;然后输出两个数字的求和结果。 import java.util.Scanner; public class HelloJava {public static void sum(){System.out.print…

kafka集群搭建(Linux环境)

zookeeper搭建&#xff0c;可以搭建集群&#xff0c;也可以单机&#xff08;本地学习&#xff0c;没必要搭建zookeeper集群&#xff0c;单机完全够用了&#xff0c;主要学习的是kafka&#xff09; 1. 首先官网下载zookeeper&#xff1a;Apache ZooKeeper 2. 下载好之后上传到…

以数据要素为支点,兴业银行撬动企业“技术杠杆”

文 | 螳螂观察 作者 | 李永华 推荐理财产品&#xff0c;恰好符合客户能承受的风险水平和想要的收益率水平&#xff0c;在资金投入上也契合客户当下的财务安排&#xff0c;于是顺利成交&#xff1b; 为客户办理的信用卡&#xff0c;优惠的场景方向与客户常常消费的领域大体一…

RWEQ模型教程

详情点击链接&#xff1a;基于“RWEQ”集成技术在土壤风蚀模拟与风蚀模数估算、变化归因分析中的实践应用及SCI论文撰写 前沿 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的…

U盘安装CentOS7.9出错:进入 dracut问题和解决方法

U盘安装CentOS7.9出错&#xff1a;进入 dracut问题和解决方法 原因&#xff1a;U盘名称未识别&#xff0c; 解决&#xff1a;进入启动界面&#xff0c;按e进入编辑界面 修改&#xff1a; vmlinuz initrdinitrd.img inst.stage2hd:LABELCentOS\x207\x20x86_64.check quiet 为 …

前端框架学习-Vue(二)

最近在学习Vue框架&#xff0c;Vue中的内容很多。相当于把之前后端的MVC&#xff0c;V层转移到前端来编写和部署。下面是学习Vue时的大纲。 Vue生命周期是Vue应用的生命周期Vue脚手架&#xff0c;即vue-cli&#xff0c;使用node.js 来创建和启动vue项目Vue组件知识&#xff0c;…

java重试机制实现方案

本文内容是目前团队内小磊同学对重试机制实现方案的梳理总结。 从为什么需要重试的背景开始&#xff0c;到重试的场景&#xff0c;大致的一些设计思路&#xff0c;最后通过两个成熟的retry组件进行案例讲解&#xff0c;理论实战。 背景 重试是系统提高容错能力的一种手段。在一…

Windows下Nginx安装与配置教程

一、前言 1、Nginx是什么&#xff1f; Nginx是一个开源的Web服务器&#xff0c;同时Nginx也提供了反向代理和负载均衡的功能。 Nginx通常作为负载均衡器暴露在外网接受用户请求&#xff0c;同时也使用其反向代理的功能&#xff0c;将用户的请求转发到实际提供服务的内网服务器…

带wiringPi库的交叉编译 ---宿主机x86Ubuntu,目标机ARMv8 aarch64(香橙派)

带wiringPi库的交叉编译如何进行 先交叉编译wiringPi库&#xff0c;编译出的库适合香橙派&#xff0c;这时候交叉编译可执行程序的平台和链接库的格式也是正确的&#xff0c;然后通过-I和-L来指定链接的wiringPi库的头文件和库的位置&#xff0c;但是现在还没有学习过&#xf…

基于正交滤波器组的语音DPCM编解码算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...........................................................g0zeros(1,lenH); g1zeros(1,l…