进程控制(一)进程创建与终止

本文详细介绍了Linux系统中的fork函数,包括其工作原理、返回值、写时拷贝的概念以及进程创建的两种方式。还探讨了进程终止的不同场景和方法,如main函数的return、exit和_exit函数的区别,以及异常退出时errno和perror的使用。
摘要由CSDN通过智能技术生成

进程创建

又识fork函数

目前我们知道在Linux下创建进程有两种方式:

  1. 命令行启动命令(自己写的程序、指令…)
  2. fork()函数

我们知道在linux中fork函数是非常重要的函数,它从一个已经存在进程中创建一个新进程。新进程为子进程,而原来的进程为父进程。

在这里插入图片描述
进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
创建子进程的进程控制块task_struct;
创建子进程的进程地址空间mm_struct;
重新申请空间,进行拷贝,修改页表;
#include<stdio.h>
#include<unistd.h>
 
int main()
{
   printf("Before:PID is %d\n", getpid());
   pid_t id = fork();
   if(id == -1)
   {
     printf("fork error!!\n");
   }
   printf("After : PID is %d, fork return is %d\n", getgid(), id);                                                                                                           
   sleep(1);
 
   return 0;
}

在这里插入图片描述

调用fork函数之前,Before输出一次,由父进程输出;
调用fork函数之后,After输出两次,由父、子进程分别输出一次。
所以fork之前父进程单独执行,fork之后,父子两个进程分别执行。且fork之后,谁先执行不确定,由调度器自行决定。

fork函数返回值

#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程pid,出错返回-1

写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

在这里插入图片描述

  • 为什么数据要进行写时拷贝?
    使父、子进程之间是相互独立的;
    节省内存和系统资源,提高 fork 的效率,减少 fork 失败的概率。
  • 为什么不一开始就复制出来一份给子进程?
    所有的数据,父、子进程并不是都要进行写入的,有的仅仅需要读取,而此时拷贝是没有意义的,而且还会浪费内存资源。

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

进程终止

进程退出的三种场景

进程退出只有三种情况:

  1. 代码运行完毕,结果正确。
  2. 代码运行完毕,结果不正确。
  3. 代码异常终止。

进程常见退出方法

main函数return 0

我们之前在写C\C++代码的时候,总会在main函数的结尾写上一个return 0但这是为什么呢??
首先我们需要明确知道,不一定非要return 0,也可以return其他值。

  1. main函数执行到return 语句时,表示该进程执行完毕;
  2. 其余函数执行到return 语句时,表示该函数执行完毕;
  3. 程序正常执行完毕并且结果正确时返回0;
  4. 程序正常执行完毕但结果不正确时返回!0;

那么程序既然正常执行完毕,但结果不正确时,会返回什么!=0,那就是1 2 3 4 5,那么他们又有什么含义,我们看下面的一段代码
在这里插入图片描述

在这里插入图片描述
看来0代表的就是success, 并且在134号以后就是unknown error

在这里插入图片描述


查看最近进程的退出码:
指令:echo $?

在这里插入图片描述

在这里插入图片描述
第一次返回100是我们所写的进程返回值;
第二次返回0是我们第一次使用echo $?成功的返回值;

使用exit函数

该函数使用时需要包头文件#include<stdlib.h>有一个int类型的参数,其含义是错误码,和main函数的return值是一个意思
在这里插入图片描述
但exit函数和return是有一定的区别的

  • exit用于结束正在运行的整个程序,它将参数返回给操作系统,把控制权交给操作系统;而return 是退出当前函数,返回函数值,把控制权交给调用函数;
  • exit是系统调用函数,表示一个进程的结束;return 是语言级别的,表示调用堆栈的返回;
  • exit函数在程序任一地方使用都可以直接退出程序,并且返回错误码
    任意地点调用exit函数,表示进程退出,不进行后续操作

使用_exit函数

在这里插入图片描述

它和exit一样都是终止进程,并且参数都一样含义也一样但差别在哪??

我们观察下面的运行结果


在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述
OK了,我们好像发现了其中的奥秘
第一个打印出来了,而第二个没有打印。


对于printf函数,我们要么碰到\n刷新条件、要么手动fflush刷新、要么终止进程(main函数中的return 也可以做到)即将结束自己刷新。
对于printf函数来说,如果没有换行\n的话,那么其先会保存在缓冲区中,但我们使用exit函数的话退出进程时却被打印出来了,也就是说exit函数会对缓冲区进行刷新,而_exit函数并没有使其打印出来也就是说没有这个功能。
既然exit函数是C的库函数,而_exit函数是系统调用函数,那么我们不妨大胆猜想,缓冲区是由C来维护的

总结

  1. exit是库函数、_exit是系统调用函数
  2. exit终止进程的时候会自动刷新缓冲区、_exit终止进程时,不回刷新缓冲区

异常退出

数组越界
野指针访问

此时运行程序后,程序会退出。这时候再去使用指令: echo $?就没有意义了!
就好比考试作弊后,无论你100,20分结果都是一样的。没有意义!

全局变量errno
在这里插入图片描述
用于显示错误信息的字符串。当程序运行时,errno宏被设置为0,一旦程序发生了系统级的错误,errno宏就会被设置为其它值。


perror函数
在这里插入图片描述
函数用于打印最近的库函数执行出错的消息

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

int main() 
{
    FILE *file = fopen("non_existent_file.txt", "r");
    if (file == NULL) 
    {
        // errno 现在已被 fopen 设置,描述错误的原因
        perror("Error opening file"); // 打印 "Error opening file: <系统的错误消息>"
        return EXIT_FAILURE;
    }

    // 正常处理文件
    fclose(file);
    return EXIT_SUCCESS;
}

输出

Error opening file: No such file or directory

尝试打开一个不存在的文件,由于文件不存在,fopen() 将返回 NULL 并设置全局变量 errno。然后我们调用 perror() 来打印错误消息,该消息将包括由 errno 表示的特定错误原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值