hello,my friend!今天,我们要开始学习新的内容了--->进程控制,进程控制涉及到操作系统如果管理和控制运行在计算机系统内的进程。我们将从fork函数,Linux进程退出,Linux进程等待,Linux进程替换等方面学习。那么接下来我们就开始敲黑板了!!
话不多说,上码!!
#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上浅浅运行一下:
这里,我有几个问题:
fork是操作系统创建子进程函数,函数在return返回时,已经把创建子进程该做的工作全部做完了(核心代码走就跑完了),也就是说:在return返回之前,子进程已经被创建好了,所以此时会存在父子两个进程。那么有两个返回值也就不奇怪了。
但是,是子进程先返回,还是父进程先返回,完全取决于调度器调度的顺序。
fork创建子进程会做如下的工作。
在现实生活中,一个爹可能有不止一个儿子,但一个儿子仅有一个爹(亲爹)。孩子想要找到父亲很简单,但是父亲想要找到儿子,得需要儿子的名字。
人亦如此,进程亦如此,所以父进程返回子进程的pid,子进程只需要返回0即可!!
返回的本质就是写入,所以谁先返回,谁就先写入fd。因为进程具有独立性,所以会发生写时拷贝
(创建一块新的内存空间,对数据进行修改)。
既然有两个进程,并且两个进程的fd值不同,那么if,else if同时进行就很正常了。
我们在写C/C++代码时,总喜欢在main函数最后return 0是什么意思?为什么总是返回0呢?
这里返回的0在系统中我们称为进程退出时的退出码,可以用来标定我们进程退出时的结果是否正确。
我们写代码是为了完成翁某项事请,那么我们怎么知道任务完成的如何呢?就是靠进程退出码
上码:
#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 $?: 用于记录最近的进程在命令行中运行的退出码,?是一个相当于一个环境变量。
如何设定我们退出时的退出码呢?
失败的原因有很多种,成功的情况只有一种。人们仅关心失败的原因,不关心成功的原因。
一般,我们用0表示成功,!0表示失败
但是,单纯的数字对计算机友好,但对人类不友好。所以,退出码要有对应的文字描述。1.可以自定义,2.使用系统中的退出码集。
系统进程退出码集
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
进程退出的场景:
正常退出,可以在命令行中使用echo $?查询退出码
异常退出
头文件:stdlib.h
功能:为退出程序的函数
用法:
注意:括号内的参数都将返回给操作系统;
return() 是返回到上一级主调函数,不一定会退出程序;
实例1
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("hello world\n");
exit(1);
}
exit和return的区别
_exit是操作系统接口
函数原型:
#include <unistd.h> void _exit(int status);
用法:
实例:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello world\n");
_exit(1);
}
exit_和exit的区别
exit的底层是用exit实现的,这两个的区别在于:调用exit会主动刷新缓冲区,调用_exit不会主动刷新缓冲区
代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello world");
sleep(1);
_exit(1);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
printf("hello world");
sleep(1);
exit(1);
}
#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);
}
}
运行一下:
#include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为nullptr
这个系统调用接口有些复杂。
#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:
我们先尝试写一下代码:
#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: 这是一个输出型参数由操作系统填充。输出子进程退出时的退出信息:退出码。退出信号等。
我们先从如何使用入手:
int status=0;
// 现在调用处;定义一个int类型的整数
pid_t id=waitpid(fd,&status,0);
//然后将指针传入
这里我们根据status会取到子进程退出信号,退出码core dump等等信息。为什么一个变量可以获得这么多的信息呢?因为他是一个4字节类型,有32位比特位,所以我们分区块,获取数据。
status不能只简单的当做整型来看待,要当做位图来看(只研究status低16位),如图所示:
所以:上面的代码应该这样写:
#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));
}
}
等待方式大概分为两种:阻塞式等待和非阻塞式等待。但都是父进程等待子进程退出。
我们讲一个小故事:
张三是一名大学生,读的专业为计算机专业。临近期末,又到了补课的时候了,但是平时张三压根没有好好听过课,所以他决定请同班学习比较好的李四给他稍微辅导一下,经过几天的挑灯夜战,站三终于有惊无险的过了考试,为了对李四的帮助表示感谢,所以他想请李四吃一顿饭,他来到了李四的宿舍楼下,打电话给李四说:“现在有空吗?请你吃个饭”。李四说:“我现在在敲代码,马上敲完,你先等一下吧”。张三说:"好,你先别挂电话,一直通着,你敲完了,马上通知我"。就这样,张三在楼下晃悠了半个小时,电话也通了半个小时,李四敲完了,终于两人可以去吃饭了。
又过了几天,高数又要考试了,上次是数据结构。张三又要麻烦李四给他补课了,李四觉得张三这个人挺好,于是毫不犹豫的帮助了张三,结果也没有让两人失望,张三取得了好成绩。张三觉得有必要还得请李四吃顿饭,有了上次不愉快的经历,张三决定这次在自己宿舍里等李四,张三拨通了电话:"我想请你吃个饭,有时间吗"?李四说:“我现在在洗衣服,你等我一会吧”。张三说:"行,我每隔五分钟给你去一次电话,然后我这边还可以做些其他的事情"。就这样,张三打了5个电话,李四还没有忙完;等到打第六了电话时,李四说:“忙忘了,走吧”。就这样,两人约着吃饭去了。
张三就相当于父进程,李四就相当于子进程。在第一次请李四吃饭时,张三一直等待着李四,就是单纯的等待,什么都没有干。这种方式叫做阻塞式等待。在第二次请李四吃饭时,张三每个一段时间就打电话询问李四的状态,不妨碍自己做其他的事情。这种方式叫做非阻塞式等待。
阻塞等待,表示一直干等着,等的时候什么事情都不干;非阻塞等待每隔一段时间等待,她没好,过几分钟再等待。(比如打电话这个例子)非阻塞等待可能需要多次检测,这是基于 非阻塞等待的轮循方案。
如果子进程已经退出,调用wait/waitpid会立即返回,并释放资源,获得子进程退出信息。 如果在任意是时刻都调用wait/waitpid,子进程存在且正常运行,进程可能阻塞。 如果不存在该子进程,则立即出错返回。
在使用非阻塞等待的方式下:
非阻塞等待的代码实现:
#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);
}
}
阻塞等待的实现代码
#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;
}
}
}
写在最后:
因作者水平有限,难免会有错误,请各位批评指正!!