🍎作者:阿润菜菜
📖专栏:Linux系统编程
在linux中fork是一个很重要的函数,它可以已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
fork函数返回两个值,一个是子进程的进程号(pid),另一个是0。父进程可以通过pid来区分自己和子进程,子进程可以通过返回值0来判断自己是子进程 。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。
下面是一个fork函数的用法示例:
#include
#include int main() {int pid = fork(); // 产生两个进程,一个父进程,一个子进程if (pid == -1) return -1;if (pid) { // 这里运行父进程代码printf("I am father, my pid is %d\n", getpid());return 0;}else { // 这里运行子进程代码printf("I am child, my pid is %d\n", getpid());return 0;}
}
fork的常见用法有那些?
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用execp()函数
在进程调用fork函数之后,当执行的程序代码转移到内核中的fork代码后,内核需要分配新的内存块和内核数据结构给子进程,内核数据结构包括PCB、mm_struct和页表,然后构建起映射关系,同时将父进程内核数据结构中的部分内容拷贝到子进程,并且内核还会将子进程添加到系统进程列表当中,最后内核空间中的fork代码执行完毕,操作系统中也就已经创建出来了子进程,最后返回用户空间,父子进程执行程序fork之后的剩余代码。
也就是说fork之前父进程独立执行,fork之后,父子进程两个执行流一起执行fork之后的剩余代码。fork之后,父子进程谁先执行,完全由调度器 决定。
参考
哈希表是一种数据结构,它可以快速地存储和查找数据。哈希表的原理是将数据的键值通过一个哈希函数转换为一个索引,然后将数据存储在对应索引的位置。当需要查找数据时,只需要再次计算键值的哈希值,就可以直接定位到数据的位置。
Linux系统中,每个Shell都有一个哈希表,用来记录执行过的命令及其路径。这样可以避免每次执行命令时都要在PATH路径下搜索命令的位置,提高了命令的执行效率。
当Linux系统创建一个子进程时,它会为子进程分配一个唯一的进程标识符(PID),并将子进程添加到系统进程列表中。系统进程列表实际上是一个哈希表,它以PID为键值,以指向进程控制块(PCB)的指针为数据。PCB是一个结构体,它包含了进程的各种信息和状态。通过这个哈希表,系统可以快速地根据PID找到对应的PCB,并进行相应的操作。
fork函数被调用一次,但返回两次,分别在父进程和子进程中返回。这两个返回值的作用是让父进程和子进程能够区分自己的身份,并进行不同的操作。
fork函数的返回值有三种可能:
示例代码:
#include
#include
#include int g_value = 100; //全局变量int main()
{// fork在返回的时候,父子都有了,return两次,id是不是pid_t类型定义的变量呢?返回的本质,就是写入!// 谁先返回,谁就让OS发生写时拷贝pid_t id = fork();assert(id >= 0);if(id == 0){//childwhile(1){printf("我是子进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, &g_value : %p\n",\getpid(), getppid(), g_value, &g_value);sleep(1);g_value=200; // 只有子进程会进行修改}}else{//fatherwhile(1){printf("我是父进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, &g_value : %p\n",\getpid(), getppid(), g_value, &g_value);sleep(1);}}
}
问题1 :那fork返回之后,为什么给父进程返回子进程的pid,而给子进程返回0呢?
我们知道子进程只能有一个父进程。但是父进程可以有多个子进程,那么父进程找子进程是不具有唯一性的,所以就需要fork函数返回子进程的pid,通过子进程的pid来确定和找到具体的子进程。
问题2: 那么一个id变量,怎么能保存两个值,并且if和elseif语句同时执行呢?
在fork之后,父子进程谁先执行代码完全由调度器决定,所以父子进程谁先返回,那么就谁先对id变量进行赋值,后一个执行的进程又会对id变量进行写入,因为进程具有独立性,所以这个时候就会发生写时拷贝。
此时id变量打印出来的地址是相同的,但是内容就会不一样了,因为分别在父子进程中各有一个id变量的值了,他们的值是不同的。
参考虚拟地址空间知识
一般两个原因:
1、物理内存不够了用了;创建子进程也是需要消耗物理内存的!
2、系统中创建了太多的进程。父进程创建子进程的上限到了,OS为了限制用户父进程无限制的创建子进程,通常都会给父进程设置一个"进程上限";
最好最直接的解决办法:重启你的云服务器
首先,**OS为什么要有进程等待?**回答这个问题之前,我们先要搞清楚为什么要创造子进程,那当然是需要子进程帮助我们执行特定的任务,既然子进程帮助我们执行了任务,那么我们当然要关心一下子进程的执行结果,所以这个问题的回答就是:
那么什么是进程等待呢?简单说就是:
进程等待(本质) — 通过系统调用,获取子进程退出码或者退出信号的方式(获取子进程退出码以此来知晓父进程交代给子进程的任务子进程完成的怎么样),并顺便释放内存(释放掉子进程的空间)
这里我们可以使用系统调用wait()/waitpid();
来实现!
如果子进程退出,父进程不读取子进程退出的信息,那么子进程就会变为僵尸进程,从而导致内存泄露的问题。其实我们可以通过进程等待的方式来解决僵尸进程问题。通过wait函数回收僵尸进程剩余资源。
让父进程通过进程等待的方式,回收子进程剩余资源(PCB,内核栈等),获取子进程退出信息,父进程需要知道子进程的退出码和执行时间等信息,形象化的比喻就是父进程通过进程等待来给僵尸进程收尸。
pid_t wait(int*status);
Linux下包含头文件:sys/types.h和sys/wait.h
参数: 输出型参数用于获取进程退出码和信号(如果程序正常运行结束的话,信号是0);当然如果我们不关心进程的退出码和信号的话,我们可以将其设置为NULL;
返回值: 等待成功:返回等待进程的pid;等待失败(如父进程没有子进程),返回-1;
当父进程调用wait函数时,父进程也会自动的等待子进程运行完毕!相当于在子进程运行的这段期间,父进程相当于卡在了wait函数内部!这叫做阻塞等待!父进程会被OS放入一个等待队列中进行等待子进程的运行结束!
输出型参数status
是一个用于接收函数返回值的变量,通常用于一些进程管理或系统调用的函数中,比如wait或waitpid。这些函数会在子进程结束时返回子进程的退出状态信息,而status参数就是用来存储这些信息的。status参数是一个指针类型,但不是要传递一个指针参数,而是一个由操作系统填充的输出型参数。status参数只使用了低16位,高两个字节没有用到,**其中的前7比特位代表子进程终止信号,后8比特位代表进程退出码。**如果传递NULL,表示不关心子进程的退出状态信息。
示例代码:
#include
#include
#include
#include
#include
#include
int main()
{
pid_t id = fork();
assert(id!=-1);
if(id==0)
{
// child process
int cnt=5;
while(cnt)
{
printf("child process running,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt--);
}exit(10);
}
int status=0;
int ret=waitpid(id,&status,0);
if(ret>0)
{
// printf("wait success,exit code:%d,signal number:%d\n",);
printf("status:%d\n",status);
}
return 0;
}
结果:
这里status
的输出结果为2560,写成16比特位的形式就是0000 1010 0000 0000,上面说了前7比特位代表子进程终止信号,后8比特位代表进程退出码,所以1010实际上就是10,也就是僵尸进程的退出码,表示什么样的结果错误,这可以取决于我们自己,我们可以自己写个printf语句输出想输出的错误信息,然后可以看到终止信号是0 ,表示僵尸进程正常退出。
上一篇:C语言基础笔记2
下一篇:深度学习7. 卷积的概念