Linux 第十九章

本文介绍了C语言中的进程创建方法(如fork)、写时拷贝的工作原理,以及main函数返回值和errno在表示进程状态和错误情况中的作用。还讨论了异常处理和错误码与退出码的区别,以及Linux系统调用中的errno应用。
摘要由CSDN通过智能技术生成

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数C初学者入门训练题解CC的使用文章「初学」C++linux

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

mm_struct

写时拷贝

fork

fork常规用法​​​​​​​

fork调用失败的原因

创建一个多进程

进程终止

main函数的返回值

strerror

​​​​​​​有时候用echo $?显示退出码并不和库里标准的对应

C语言的错误码


mm_struct

写时拷贝

父进程创建子进程的时候,首先将自己的页表读写权限改为只读,然后再创建子进程

(注意:代码段一直是只读的),但是这个过程用户是不知道的!用户就有可能对某一批数据进行写入!

此时,页表就会因为权限问题出错

1)真的出错了,例如用户想要修改代码的数据

2)例如用户想要修改数据段的数据,操作系统就会重新申请内存

注意:

1.在地址空间里面绝对不会出现划分的区域重叠的

2.Os操作系统肯定知道我们在读还是在写

为什么子进程发生写时拷贝的时候,需要拷贝父进程数据段的代码再进行写入,而不是直接申请空间直接写入呢?

子进程不一定要对父进程数据段全部进行修改,可能修改一部分

fork

fork常规用法​​​​​​​

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段,例如,父进程等待客户端请求,生成子进程来处理请求。

2.一个进程执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

fork失败的返回值小于0

1.系统进程太多

2.实际用户的进程数超过了限制

创建一个多进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#define N 10
typedef void (*callback_t)();//函数指针

void Worker()//子进程执行的方法
{
        int cnt=10;
        while(cnt)
        {
                printf("I am child process,pid:%d ,ppid: %d ,cnt:%d\n",getpid(),getppid(),cnt);
                sleep(1);
                cnt--;
        }


}
void createSubProcess(int n,callback_t cb)//创建n个子进程+
{


        int i=0;
        for(i=0;i<n;i++)
        {
                sleep(1);
                pid_t id=fork();
                if(id==0)
                {
                        printf("create child process success:%d\n",i);
                        //child
                        cb();
                        exit(0);
                }
        }


}


int main()
{
        createSubProcess(N,Worker);
        //只有父进程走到这里
        sleep(100);
        return 0;
}

当n个子进程运行完之后,父进程并没有结束(因为sleep(100)),所以子进程就会形成僵尸进程
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
17831 26353 26353 17831 pts/0    26353 S+    1000   0:00 ./myprocess
26353 26380 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26458 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26540 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26555 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26629 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26711 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26758 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26808 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26882 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>
26353 26929 26353 17831 pts/0    26353 Z+    1000   0:00 [myprocess] <defunct>

进程终止

main函数的返回值

[BCH@hcss-ecs-6176 ~]$ cat test.c
#include<stdio.h>
int main()
{
        return 10;
}

[BCH@hcss-ecs-6176 ~]$ ./mytest
[BCH@hcss-ecs-6176 ~]$ echo $?//获取main函数的返回值
10

?:保存的就是最近一个子进程执行完毕时的退出码
就类似于 echo $环境变量 将环境变量的内容输出到屏幕上
echo $?将最近一个子进程执行完毕时的退出码输出到屏幕上

在多进程环境中,我们(父进程)创建子进程的目的是什么?
帮我们办事


子进程把事情办得怎么样呢??

main函数的返回值,就叫做进程的退出码,0->成功,非0表示失败
非0的时候,这个进程因为什么原因失败!
1,2,3,4,5,6……我们可以用不同的数字表示不同的原因!!

纯数字能表示出错原因,但是不便于人阅读,exit code->exit code string (将数字转化为字符串的形式便于人去阅读)

strerror

在C语言中,strerror是一个库函数,用于将错误码或退出码转换为对应的错误消息字符串。

通过strerror查看linux中c的退出码(错误码)
[BCH@hcss-ecs-6176 ~]$ cat test.c
#include<stdio.h>
#include<string.h>
int main()
{
        int i=0;
        for(i=0;i<200;i++)//之所以是200,因为我不知道linux中退出码的范围(实际上退出码的范围0-133)
        {
                printf("%d:%s\n",i,strerror(i));
        }
        return 10;
}

[BCH@hcss-ecs-6176 ~]$ ./mytest
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
134:Unknown error 134//这里已经没有了

连续echo $?

[BCH@hcss-ecs-6176 ~]$ echo $?
10
[BCH@hcss-ecs-6176 ~]$ echo $?
0
[BCH@hcss-ecs-6176 ~]$ echo $?
0

为什么第一个echo $?输出的就是10,第二个echo $?输出就是0呢?

因为第一个echo $?中的?保留的是./mytest进程的退出码,而./mytest进程的退出码是10,

第二个echo $?中的?保留的是第一个echo $?这个进程的退出码,因为第一个echo $?进程退出码是0

​​​​​​​有时候用echo $?显示退出码并不和库里标准的对应

[BCH@hcss-ecs-6176 ~]$cd /home/wcq
bash: cd: /home/wcq: Permission denied
[BCH@hcss-ecs-6176 ~]$echo $?
1
库里13:Permission denied,Permission denied表示的退出码是13
但是echo $?显示的值是1
所以这里用退出码不是使用的C语言标准库的,而是使用的自定义的

main函数的退出码是可以被父进程获取的,用来判断子进程的运行结果

C语言的错误码

c语言中的全局变量errno
在C语言中,errno是一个全局变量,用于表示最近一次发生的错误码。它定义在 <errno.h> 头文件中。


错误码VS退出码
错误码通常是衡量一个库函数或者是一个系统调用一个函数的调用的情况
退出码通常是一个进程退出的时候,他退出结果

当失败的时候,用来衡量函数,进程出错时的出错详细信息原因

linux内核是c语言写的,linux中很多的系统调用就设置了errno

一般代码出异常,代码是没有运行完的

异常问题——引入一下
[BCH@hcss-ecs-6176 ~]$ cat test.c
#include<stdio.h>
#include<string.h>
int main()
{
        int a=10;
        a/=0; 
}
[BCH@hcss-ecs-6176 ~]$ ./mytest
浮点数例外//异常

[BCH@hcss-ecs-6176 ~]$ cat test.c
#include<stdio.h>
#include<string.h>
int main()
{
        
        int *p=NULL;
        *p=100;
        return 10;
}
[BCH@hcss-ecs-6176 ~]$ ./mytest
段错误//异常
进程异常会被os杀掉这个进程(kill)
进程异常的时候,会转化成信号,会被os检测到的

结论:进程出异常,本质是进程收到了对应的信号,自己终止了!!

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值