Linux进程控制

目录

fork函数

[1.问题 一](#1.问题 一)

2.问题二

3.问题三

fork常见用法

fork失败原因

进程退出

1.退出码

2.进程终止

进程终止常见方法:

exit函数

_exit函数

进程等待

1.为什么要进行进程等待

2.见一见猪跑

3.进程等待的方法

1.wait

2.waitpid


hello,my friend!今天,我们要开始学习新的内容了--->进程控制,进程控制涉及到操作系统如果管理和控制运行在计算机系统内的进程。我们将从fork函数,Linux进程退出,Linux进程等待,Linux进程替换等方面学习。那么接下来我们就开始敲黑板了!!

fork函数

话不多说,上码!!

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int num = 0;
int wer = 100;
int main()
{
    pid_t fd = fork();
    if (fd < 0)
    {
        printf("fork fail!\n");
    }
    else if (fd == 0)
    {
        while (2)
        {
            printf("我是子进程,wer:%d,&wer:%p\n", wer, &wer);
            num++;
            if (num == 10)
            {
                printf("wer数据已被修改,修改是由子进程完成的\n");
                wer=300;
            }
            sleep(1);
        }
    }
    else
    {
        while (1)
        {
            printf("我是父进程,wer:%d,&wer:%p\n", wer, &wer);
            sleep(3);
        }
    }
    return 1;
}

在Linux上浅浅运行一下:

这里,我有几个问题:

  1. 如何理解fork有两个返回值?
  2. 如何理解fork函数返回之后,给父进程返回子进程的pid,给子进程返回0这种现象?
  3. 如何理解一个fd会保存两个值这种现象?并让if else if同时进行?

1.问题 一

fork是操作系统创建子进程函数,函数在return返回时,已经把创建子进程该做的工作全部做完了(核心代码走就跑完了),也就是说:在return返回之前,子进程已经被创建好了,所以此时会存在父子两个进程。那么有两个返回值也就不奇怪了。

但是,是子进程先返回,还是父进程先返回,完全取决于调度器调度的顺序。

fork创建子进程会做如下的工作。

  • 创建子进程的PCB
  • 赋值
  • 创建子进程的地址空间
  • 赋值
  • 创建页表并建立映射关系
  • 将子进程放入进程队列list

2.问题二

在现实生活中,一个爹可能有不止一个儿子,但一个儿子仅有一个爹(亲爹)。孩子想要找到父亲很简单,但是父亲想要找到儿子,得需要儿子的名字。

人亦如此,进程亦如此,所以父进程返回子进程的pid,子进程只需要返回0即可!!

3.问题三

返回的本质就是写入,所以谁先返回,谁就先写入fd。因为进程具有独立性,所以会发生写时拷贝

(创建一块新的内存空间,对数据进行修改)。

既然有两个进程,并且两个进程的fd值不同,那么if,else if同时进行就很正常了。

fork常见用法

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

fork失败原因

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

进程退出

1.退出码

我们在写C/C++代码时,总喜欢在main函数最后return 0是什么意思?为什么总是返回0呢?

这里返回的0在系统中我们称为进程退出时的退出码,可以用来标定我们进程退出时的结果是否正确。

我们写代码是为了完成翁某项事请,那么我们怎么知道任务完成的如何呢?就是靠进程退出码

上码:

cpp 复制代码
#include<stdio.h>
int addfromto(int from,int to)
{
    int sum=0;
    for(int i=from;i<=to;i++)
    {
        sum+=i;

    }
    return sum;

}
int main()
{
    int ret=addfromto(1,100);
    if(ret==5050)
        return 0;
    else
        return 1;
}

这里我们隆重介绍一下:echo $?

./my.out 运行的一个进程。

echo $?: 用于记录最近的进程在命令行中运行的退出码,?是一个相当于一个环境变量。

如何设定我们退出时的退出码呢?

  1. 如果不关心进程退出码,返回0即可。
  2. 如果我们要关心进程退出时的退出码,要返回特定的数组标识特定的错误。

失败的原因有很多种,成功的情况只有一种。人们仅关心失败的原因,不关心成功的原因。

一般,我们用0表示成功,!0表示失败

但是,单纯的数字对计算机友好,但对人类不友好。所以,退出码要有对应的文字描述。1.可以自定义,2.使用系统中的退出码集。

系统进程退出码集

cpp 复制代码
0:Success
1:Operation not permitted
2:No such file or directory
3:No such process
4:Interrupted system call
5:Input/output error
6:No such device or address
7:Argument list too long
8:Exec format error
9:Bad file descriptor
10:No child processes
11:Resource temporarily unavailable
12:Cannot allocate memory
13:Permission denied
14:Bad address
15:Block device required
16:Device or resource busy
17:File exists
18:Invalid cross-device link
19:No such device
20:Not a directory
21:Is a directory
22:Invalid argument
23:Too many open files in system
24:Too many open files
25:Inappropriate ioctl for device
26:Text file busy
27:File too large
28:No space left on device
29:Illegal seek
30:Read-only file system
31:Too many links
32:Broken pipe
33:Numerical argument out of domain
34:Numerical result out of range
35:Resource deadlock avoided
36:File name too long
37:No locks available
38:Function not implemented
39:Directory not empty
40:Too many levels of symbolic links
41:Unknown error 41
42:No message of desired type
43:Identifier removed
44:Channel number out of range
45:Level 2 not synchronized
46:Level 3 halted
47:Level 3 reset
48:Link number out of range
49:Protocol driver not attached
50:No CSI structure available
51:Level 2 halted
52:Invalid exchange
53:Invalid request descriptor
54:Exchange full
55:No anode
56:Invalid request code
57:Invalid slot
58:Unknown error 58
59:Bad font file format
60:Device not a stream
61:No data available
62:Timer expired
63:Out of streams resources
64:Machine is not on the network
65:Package not installed
66:Object is remote
67:Link has been severed
68:Advertise error
69:Srmount error
70:Communication error on send
71:Protocol error
72:Multihop attempted
73:RFS specific error
74:Bad message
75:Value too large for defined data type
76:Name not unique on network
77:File descriptor in bad state
78:Remote address changed
79:Can not access a needed shared library
80:Accessing a corrupted shared library
81:.lib section in a.out corrupted
82:Attempting to link in too many shared libraries
83:Cannot exec a shared library directly
84:Invalid or incomplete multibyte or wide character
85:Interrupted system call should be restarted
86:Streams pipe error
87:Too many users
88:Socket operation on non-socket
89:Destination address required
90:Message too long
91:Protocol wrong type for socket
92:Protocol not available
93:Protocol not supported
94:Socket type not supported
95:Operation not supported
96:Protocol family not supported
97:Address family not supported by protocol
98:Address already in use
99:Cannot assign requested address
100:Network is down
101:Network is unreachable
102:Network dropped connection on reset
103:Software caused connection abort
104:Connection reset by peer
105:No buffer space available
106:Transport endpoint is already connected
107:Transport endpoint is not connected
108:Cannot send after transport endpoint shutdown
109:Too many references: cannot splice
110:Connection timed out
111:Connection refused
112:Host is down
113:No route to host
114:Operation already in progress
115:Operation now in progress
116:Stale file handle
117:Structure needs cleaning
118:Not a XENIX named type file
119:No XENIX semaphores available
120:Is a named type file
121:Remote I/O error
122:Disk quota exceeded
123:No medium found
124:Wrong medium type
125:Operation canceled
126:Required key not available
127:Key has expired
128:Key has been revoked
129:Key was rejected by service
130:Owner died
131:State not recoverable
132:Operation not possible due to RF-kill
133:Memory page has hardware error

2.进程终止

进程退出的场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程终止常见方法:

正常退出,可以在命令行中使用echo $?查询退出码

  • 从main函数中返回
  • 在任何地方调用exit
  • 在任何地址调用_exit

异常退出

  • 使用Ctrl+Z组合键,终止进程
  • 使用kill -9 命令杀死进程

exit函数

头文件:stdlib.h

功能:为退出程序的函数

用法:

  • exit(1); 为异常退出 //只要括号内数字不为0都表示异常退出
  • exit(0); 为正常退出

注意:括号内的参数都将返回给操作系统;

return() 是返回到上一级主调函数,不一定会退出程序;

实例1

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
    printf("hello world\n");
    exit(1);

}

exit和return的区别

  • exit是一个C语言函数,return是一个关键字。
  • exit是系统调用级别的,表示整个进程的结束;return是语言级别的,表示调用堆栈的返回。
  • exit表示进程的退出,return函数的退出。
  • exit函数结束进程,删除进程使用的内存空间,并将进程的状态返回给操作系统(一般是用0表示正常终止,非0表示异常终止);return是结束函数的执行,将函数的执行信息传其他调用函数使用
  • 非主函数中调用exit和return区别很明显,但是在main函数中调用区分不大,多数情况效果一样

_exit函数

_exit是操作系统接口

函数原型:

#include <unistd.h>
void _exit(int status);

用法:

  • _ exit(1); 为异常退出 //只要括号内数字不为0都表示异常退出
  • _exit(0); 为正常退出

实例:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("hello world\n");
    _exit(1);

}

exit_和exit的区别

exit的底层是用exit实现的,这两个的区别在于:调用exit会主动刷新缓冲区,调用_exit不会主动刷新缓冲区

代码:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("hello world");
    sleep(1);
    _exit(1);

}
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    printf("hello world");
    sleep(1);
    exit(1);

}

进程等待

1.为什么要进行进程等待

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kil 9 也无能为力,因为谁也没有办杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

2.见一见猪跑

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    int cnt = 10;
    pid_t id = fork();
    if (id == 0)
    {
        while (cnt)
        {
            printf("我是子进程,pid:%d,ppif:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    sleep(15);
    pid_t idd = wait(nullptr);
    if (id > 0)
    {
        printf("idd:%d", id);
    }
}

运行一下:

3.进程等待的方法

1.wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为nullptr

2.waitpid

这个系统调用接口有些复杂。

#include <sys/wait.h>

pid_t waitpid (pid_t pid, int* statusp, int options);

返回值:如果成功,则返回子进程的PID,如果options为WNOHANG,则返回0,如果发生其他错误,则返回-1。
pid_t pid:要等待子进程的PID。
int options:

  • 0:若pid指定的子进程没有结束,父进程挂起。若正常结束,则返回该子进程的ID。
  • WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。正常结束,则返回该子进程的ID。(异常返回-1)

我们先尝试写一下代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    int cnt = 10;
    pid_t id = fork();
    if (id == 0)
    {
        while (cnt)
        {
            printf("我是子进程,pid:%d,ppif:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status=0;
    pid_t idd = waitpid(id,&status,0);
    if (idd > 0)
    {
        printf("idd:%d,stutus:%d\n", id,status);
    }
}

这里的status为什么是256呢?

这里就和waitpid的第二个参数有关了,我们好好分析一下:


int *status:

这是一个输出型参数由操作系统填充。输出子进程退出时的退出信息:退出码。退出信号等。

我们先从如何使用入手:

cpp 复制代码
int status=0;
// 现在调用处;定义一个int类型的整数
pid_t id=waitpid(fd,&status,0);
//然后将指针传入

这里我们根据status会取到子进程退出信号,退出码core dump等等信息。为什么一个变量可以获得这么多的信息呢?因为他是一个4字节类型,有32位比特位,所以我们分区块,获取数据。

status不能只简单的当做整型来看待,要当做位图来看(只研究status低16位),如图所示:

  1. 当进程正常终止时,终止信号为0,仅注意退出码会即可。
  2. 当程序中出现错误,如:除零错误,野指针问题。操作系统会向进程发送相关信号,来杀死进程,此时仅注意终止信号即可(因为进程没有正常退出,退出码无意义)。

所以:上面的代码应该这样写:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    int cnt = 2;
    pid_t id = fork();
    if (id == 0)
    {
        while (cnt)
        {
            printf("我是子进程,pid:%d,ppif:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status=0;
    pid_t idd = waitpid(id,&status,0);
    if (idd > 0)
    {
        printf("idd:%d,sig number:%d,chid exist code:%d\n", id,(status&0x7f),((status>>8)&0xff00));
    }
}

3.阻塞式等待和非阻塞式等待(waitpid的等待方式)

等待方式大概分为两种:阻塞式等待和非阻塞式等待。但都是父进程等待子进程退出。

我们讲一个小故事:

张三是一名大学生,读的专业为计算机专业。临近期末,又到了补课的时候了,但是平时张三压根没有好好听过课,所以他决定请同班学习比较好的李四给他稍微辅导一下,经过几天的挑灯夜战,站三终于有惊无险的过了考试,为了对李四的帮助表示感谢,所以他想请李四吃一顿饭,他来到了李四的宿舍楼下,打电话给李四说:"现在有空吗?请你吃个饭"。李四说:"我现在在敲代码,马上敲完,你先等一下吧"。张三说:"好,你先别挂电话,一直通着,你敲完了,马上通知我"。就这样,张三在楼下晃悠了半个小时,电话也通了半个小时,李四敲完了,终于两人可以去吃饭了。

又过了几天,高数又要考试了,上次是数据结构。张三又要麻烦李四给他补课了,李四觉得张三这个人挺好,于是毫不犹豫的帮助了张三,结果也没有让两人失望,张三取得了好成绩。张三觉得有必要还得请李四吃顿饭,有了上次不愉快的经历,张三决定这次在自己宿舍里等李四,张三拨通了电话:"我想请你吃个饭,有时间吗"?李四说:"我现在在洗衣服,你等我一会吧"。张三说:"行,我每隔五分钟给你去一次电话,然后我这边还可以做些其他的事情"。就这样,张三打了5个电话,李四还没有忙完;等到打第六了电话时,李四说:"忙忘了,走吧"。就这样,两人约着吃饭去了。


张三就相当于父进程,李四就相当于子进程。在第一次请李四吃饭时,张三一直等待着李四,就是单纯的等待,什么都没有干。这种方式叫做阻塞式等待。在第二次请李四吃饭时,张三每个一段时间就打电话询问李四的状态,不妨碍自己做其他的事情。这种方式叫做非阻塞式等待。

阻塞等待,表示一直干等着,等的时候什么事情都不干;非阻塞等待每隔一段时间等待,她没好,过几分钟再等待。(比如打电话这个例子)非阻塞等待可能需要多次检测,这是基于 非阻塞等待的轮循方案。

如果子进程已经退出,调用wait/waitpid会立即返回,并释放资源,获得子进程退出信息。
如果在任意是时刻都调用wait/waitpid,子进程存在且正常运行,进程可能阻塞。
如果不存在该子进程,则立即出错返回。

在使用非阻塞等待的方式下:

  • 如果等待的子进程存在,但子进程没有退出,调用wait/waitpid询问时会返回0。
  • 如果等待的子进程存在,并且子进程刚好退出,调用wait/waitpid询问时会返回子进程的退出信息,如:终止信号,退出码。
  • 如果等待的子进程不存在。调用wait/waitpid时会返回0。

非阻塞等待的代码实现:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    int cnt = 5;
    pid_t id = fork();
    if (id == 0)
    {
        while (cnt)
        {
            printf("我是子进程,pid:%d,ppif:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }

    while (1)
    {
        int status = 0;
        pid_t idd = waitpid(id, &status, WNOHANG);
        if (idd > 0)
        {
            printf("idd:%d,sig number:%d,chid exist code:%d\n", id, (status & 0x7F), ((status >> 8) & 0xFF));
            break;
        }

        else if (idd == 0)
        {
            printf("waiting.......\n");
        }
        else
        {
            printf("sorry,不存在该进程\n");
            break;
        }
        sleep(1);
    }
}

阻塞等待的实现代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
    int cnt = 5;
    pid_t id = fork();
    if (id == 0)
    {
        while (cnt)
        {
            printf("我是子进程,pid:%d,ppif:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status = 0;
    
    while (2)
    {   
        pid_t idd = waitpid(id, &status, 0);
        if (idd > 0)
        {
            printf("idd:%d,sig number:%d,chid exist code:%d\n", id, (status & 0x7F), ((status >> 8) & 0xFF));
            break;
        }
    }
}

写在最后:

因作者水平有限,难免会有错误,请各位批评指正!!

相关推荐
什么半岛铁盒35 分钟前
【Linux系统】Linux环境变量:系统配置的隐形指挥官
linux
Lw老王要学习1 小时前
Linux容器篇、第一章_02Rocky9.5 系统下 Docker 的持久化操作与 Dockerfile 指令详解
linux·运维·docker·容器·云计算
橙子小哥的代码世界1 小时前
【大模型RAG】Docker 一键部署 Milvus 完整攻略
linux·docker·大模型·milvus·向量数据库·rag
倔强的石头1062 小时前
【Linux指南】用户与系统基础操作
linux·运维·服务器
云上艺旅2 小时前
centos升级内核
linux·运维·centos
kaikaile19952 小时前
centos开启samba服务
linux·运维·centos
云上艺旅2 小时前
centos部署k8s v1.33版本
linux·云原生·kubernetes·centos
好多知识都想学3 小时前
Centos 7 服务器部署多网站
linux·服务器·centos
好多知识都想学3 小时前
centos 7 部署awstats 网站访问检测
linux·运维·centos
Li-Yongjun3 小时前
深度解析 Linux 内核参数 net.ipv4.tcp_rmem:优化网络性能的关键
linux·网络·tcp/ip