Fork System Call

The fork system call is used to create a new processes. The newly created process is the child process. The process which calls fork and creates a new process is the parent process. The child and parent processes are executed concurrently.

But the child and parent processes reside on different memory spaces. These memory spaces have same content and whatever operation is performed by one process will not affect the other process.

When the child processes is created; now both the processes will have the same Program Counter (PC), so both of these processes will point to the same next instruction. The files opened by the parent process will be the same for child process.

The child process is exactly the same as its parent but there is difference in the processes ID’s:

  1. The process ID of the child process is a unique process ID which is different from the ID’s of all other existing processes.
  2. The Parent process ID will be the same as that of the process ID of child’s parent.

Properties of Child Process

The following are some of the properties that a child process holds:

  1. The CPU counters and the resource utilizations are initialized to reset to zero.
  2. When the parent process is terminated, child processes do not receive any signal because PR_SET_PDEATHSIG attribute in prctl() is reset.
  3. The thread used to call fork() creates the child process. So the address of the child process will be the same as that of parent.?
  4. The file descriptor of parent process is inherited by the child process. For example the offset of the file or status of flags and the I/O attributes will be shared among the file descriptors of child and parent processes. So file descriptor of parent class will refer to same file descriptor of child class.
  5. The open message queue descriptors of parent process are inherited by the child process. For example if a file descriptor contains a message in parent process the same message will be present in the corresponding file descriptor of child process. So we can say that the flag values of these file descriptors are same.
  6. Similarly open directory streams will be inherited by the child processes.
  7. The default Timer slack value of the child class is same as the current timer slack value of parent class.?

Properties that are not inherited by Child process

The following are some of the properties that are not inherited by a child process:

  1. Memory locks
  2. The pending signal of a child class is empty.
  3. Process associated record locks (fcntl())
  4. Asynchronous I/O operations and I/O contents.
  5. Directory change notifications.
  6. Timers such as alarm(), setitimer() are not inherited by the child class.

fork() in C

There are no arguments in fork() and the return type of fork() is integer. You have to include the following header files when fork() is used:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

When working with fork(), <sys/types.h> can be used for type pid_t for processes ID’s as pid_t is defined in <sys/types.h>.

The header file <unistd.h> is where fork() is defined so you have to include it to your program to use fork().

The return type is defined in <sys/types.h> and fork() call is defined in <unistd.h>. Therefore, you need to include both in your program to use fork() system call.

Syntax of fork()

The syntax of fork() system call in Linux, Ubuntu is as follows:

pid_t fork(void);
In the syntax the return type is pid_t. When the child process is successfully created, the PID of the child process is returned in the parent process and 0 will be returned to the child process itself.

If there is any error then -1 is returned to the parent process and the child process is not created.

  No arguments are passed to fork().

Example 1: Calling fork()

Consider the following example in which we have used the fork() system call to create a new child process:

CODE:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> int main()
{
fork();
printf("Using fork() system call\n");
return ;
}

OUTPUT:

Using fork() system call
Using fork() system call

In this program, we have used fork(), this will create a new child process. When the child process is created, both the parent process and the child process will point to the next instruction (same Program Counter). In this way the remaining instructions or C statements will be executed the total number of process times, that is 2n times, where n is the number of fork() system calls.

So when the fork() call is used one time as above (21 = 2) we will have our output 2 times.

Here when the fork() system call is used, the internal structure will look like:

Consider the following case in which the fork() is used 4 times:

CODE:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> int main()
{
fork();
fork();
fork();
fork();
printf("Using fork() system call");
return ;
}

Output:

Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call
Using fork() system call

Now the total number of process created are 24 = 16 and we have our print statement executed 16 times.

Example 2: Testing if fork() was successful

In the following example we have used the decision making construct to test the value (int) returned by fork(). And the corresponding messages are displayed:

CODE:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h> int main()
{
pid_t p;
p = fork();
if(p==-)
{
printf("There is an error while calling fork()");
}
if(p==)
{
printf("We are in the child process");
}
else
{
printf("We are in the parent process");
}
return ;
}

OUTPUT:

We are in the parent process
We are in the child process

In the above example we have used the type pid_t which will store the return value of fork(). fork() is called on line:

p = fork();

So the integer value returned by fork() is stored in p and then p is compared to check if our fork() call was successful.

When the fork() call is used and child is created successfully, the id of child process will be returned to parent process and 0 will be returned to the child process.The ID of child process in Parent process will not be the same as the ID of child process in child process itself. In child process the ID of child process will be 0.

With this tutorial you can see how to get started with the fork system call in linux.

原文摘抄自:https://linuxhint.com/fork-system-call-linux/

How to make parent wait for all child processes to finish?

pid_t child_pid, wpid;
int status = ; //Father code (before child processes start) for (int id=; id<n; id++) {
if ((child_pid = fork()) == ) {
//child code
exit();
}
} while ((wpid = wait(&status)) > ); // this way, the father waits for all the child processes //Father code (After all child processes end)

wait waits for a child process to terminate, and returns that child process's pid. On error (eg when there are no child processes), -1 is returned. So, basically, the code keeps waiting for child processes to finish, until the waiting errors out, and then you know they are all finished.

摘抄自:https://stackoverflow.com/questions/19461744/how-to-make-parent-wait-for-all-child-processes-to-finish

CoW (copy on write)

To save time on fork, the kernel only duplicate its mapping. For example we have 100 pages mapping for the array , the kernel will duplicate the TLB entries and change all the mapping to read only. If both processes only read, they work with shared memory but when one process try to write, it creates a page fault, the kernel trap handler copy the page and change the permission to read-write returning the program counter to the previous statement to try again.

If we measure the time of the first assignment , we will get a longer time:

        ...
x=fork();
if(x>){
arr[]=; // page fault, copy page and update TLB
arr[]=; // one memory access
wait(&status);
}

To test this using Ubuntu 64 bit we add a simple assembly function

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h> static __inline__ unsigned long long rdtsc(void)
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<< );
} void main(void)
{
int x,status;
long long t1,t2,t3,t4;
int arr[]={,,,,};
x=fork();
if(x>){
t1=rdtsc();
arr[]=;
t2=rdtsc();
arr[]=;
t3=rdtsc();
arr[]=;
t4=rdtsc(); printf("t1=%lld\n",t1);
printf("t2=%lld\n",t2);
printf("t3=%lld\n",t3);
printf("t4=%lld\n",t4);
wait(&status);
}
else
{
printf("child \n");
exit();
}
puts("parent only");
}

Output:

t1=
t2=
t3=
t4= t1=
t2=
t3=
t4=
 
 

As you can see from the output , the first time we write take 3 times longer as a result of a page fault.

Fail to fork?

fork fails in some situations: if we reached the maximum user processes allowed (see ulimit -a) , out of memory, no MMU and more (see man page)

It also fails if the parent process consume more than 50% of the system memory. Let take an example:

void main(void)
{
int x,status;
int arr1[];
puts("bye");
memset(arr1,,sizeof(arr1));
x=fork();
...
}

The program declares a 200MB array and clear it using memset to make it map to physical memory. As we saw, on fork the memory is not duplicate but the system check if we have the required free memory in case the child will write. If the system doesn’t have 200MB free, fork will fail

The above behaviour can create a strange bug in the following situation:

  • The parent fill a 200MB array and fork
  • On fork, the system has 250MB free – fork returns successfully
  • As a result of Copy on write mechanism , the memory is not consumed
  • Another process consume 200MB
  • The child process access the array elements and while trying to handle the page faults the kernel crashed

Lets test it on Qemu image with 512MB:

# cat /proc/meminfo

Code Example:

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h> void main(void)
{
int x,status;
int arr1[];
memset(arr1,,sizeof(arr1));
x=fork();
if(x>){
printf("fork returned:%d\n",x);
wait(&status);
else
{
int i,r;
for(i=;i<;i++){
memset(arr1+i*,,);
sleep();
puts("mem");
}
}
}
 

Run this program and after fork:

 
# cat /proc/meminfo

We can see the only 200MB consumed. Now run a simple program to consume more memory:

void main(void)
{
int x,status;
int arr1[];
puts("bye");
memset(arr1,,sizeof(arr1));
sleep();
}
 
 

and check again:

# cat /proc/meminfo

Now the child write to 4MB every 30 seconds and make a copy of the pages , when it will reach the system limit (100MB only) we will see a kernel oops:

 
app invoked oom-killer: gfp_mask=0x24200ca(GFP_HIGHUSER_MOVABLE), nodemask=, order=, oom_score_adj=
app cpuset=/ mems_allowed=
CPU: PID: Comm: app Not tainted 4.9. #
Hardware name: ARM-Versatile Express
[<8011196c>] (unwind_backtrace) from [<8010cf2c>] (show_stack+0x20/0x24)
[<8010cf2c>] (show_stack) from [<803d32a4>] (dump_stack+0xac/0xd8)
[<803d32a4>] (dump_stack) from [<8023da88>] (dump_header+0x8c/0x1c4)
[<8023da88>] (dump_header) from [<801ef464>] (oom_kill_process+0x3a8/0x4b0)
[<801ef464>] (oom_kill_process) from [<801ef8c0>] (out_of_memory+0x124/0x418)
[<801ef8c0>] (out_of_memory) from [<801f48b4>] (__alloc_pages_nodemask+0xd6c/0xe0c)
[<801f48b4>] (__alloc_pages_nodemask) from [<>] (wp_page_copy+0x78/0x580)
[<>] (wp_page_copy) from [<8021a630>] (do_wp_page+0x148/0x670)
[<8021a630>] (do_wp_page) from [<8021cdd8>] (handle_mm_fault+0x33c/0xb00)
[<8021cdd8>] (handle_mm_fault) from [<>] (do_page_fault+0x26c/0x384)
[<>] (do_page_fault) from [<>] (do_DataAbort+0x48/0xc4)
[<>] (do_DataAbort) from [<8010dec4>] (__dabt_usr+0x44/0x60)
Exception stack(0x9ecc3fb0 to 0x9ecc3ff8)
3fa0: 77aaa7f8 0007c0f0 77dff000
3fc0: 000084a0 2b095000 7e94ad04
3fe0: 722ed8f8 2b13c158 ffffffff
Mem-Info:
active_anon: inactive_anon: isolated_anon:
active_file: inactive_file: isolated_file:
unevictable: dirty: writeback: unstable:
slab_reclaimable: slab_unreclaimable:
mapped: shmem: pagetables: bounce:
free: free_pcp: free_cma:
Node active_anon:499920kB inactive_anon:8kB active_file:92kB inactive_file:124kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:184kB dirty:0kB writeback:0kB shmem:32kB writeback_tmp:0kB unstable:0kB pages_scanned: all_unreclaimable? no
Normal free:2852kB min:2856kB low:3568kB high:4280kB active_anon:499920kB inactive_anon:8kB active_file:92kB inactive_file:124kB unevictable:0kB writepending:0kB present:524288kB managed:510824kB mlocked:0kB slab_reclaimable:1828kB slab_unreclaimable:2392kB kernel_stack:344kB pagetables:1292kB bounce:0kB free_pcp:120kB local_pcp:120kB free_cma:0kB
lowmem_reserve[]:
Normal: *4kB (UE) *8kB (UME) *16kB (UME) *32kB (U) *64kB (UM) *128kB (UM) *256kB (M) *512kB *1024kB *2048kB (U) *4096kB = 2852kB
total pagecache pages
pages in swap cache
Swap cache stats: add , delete , find /
Free swap = 0kB
Total swap = 0kB
pages RAM
pages HighMem/MovableOnly
pages reserved
pages cma reserved
[ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name
[ ] syslogd
[ ] klogd
[ ] sh
[ ] app
[ ] app
[ ] eat
Out of memory: Kill process (app) score or sacrifice child
Killed process (app) total-vm:204744kB, anon-rss:203180kB, file-rss:72kB, shmem-rss:0kB
oom_reaper: reaped process (app), now anon-rss:4kB, file-rss:0kB, shmem-rss:0kB

We can see the oops generated on a page fault (do_page_fault)

To avoid such cases we need to pre fault the array (read and write its content at least one element per page) immediately after fork.

Files are not duplicated on fork

It is important to understand that file descriptor object are not duplicated on fork. In this way we can share resources between the child and the parent. All the anonymous objects (pipes, shared memory, etc.) can only be shared using the file descriptor. One way (the easy way) is declaring the resource before forking and the other way is sending the file descriptor using unix domain socket. If we open a regular file the child and the parent are using the same kernel object i.e. position, flags, permission and more are shared:

Example:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h> void main(void)
{
int x,status,fd;
fd=open("./syslog1",O_RDWR);
x=fork();
if(x>){
char buf[];
read(fd,buf,);
buf[]='\0';
puts(buf);
wait(&status);
}
else
{
char buf[];
sleep();
read(fd,buf,);
buf[]='\0';
puts(buf);
exit();
}
}
 

We open a file, read 29 chars on the parent and 29 chars on the child, the parent update the position so the child is reading where the parent ended:

Output:

 
Dec  :: developer-vir
tual-machine rsyslogd: [origi

Parent-child with different code

Sometimes we need 2 processes with parent and child relation (to send signals or share resource) but they have completely different code. In a wireless router for example we have a routing control process and a web server. We want the web server process to send a signal to the router control every time the configuration changes. We can use fork with execve to implement it:

For example: 2 processes using pipe for communication:

Parent app:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h> int main(int argc,char *argv[])
{
char buf[]="hello";
int fd,i=;
fd = atoi(argv[]);
puts("parent started:");
while()
{
i++;
sprintf(buf,"hello:%d",i);
write(fd,buf,);
sleep();
}
return ;
}
 

Compile it and call the executable parent_app

Child app:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h> int main(int argc,char *argv[])
{
char buf[];
int fd;
puts("child started");
fd = atoi(argv[]);
while()
{
read(fd,buf,);
puts(buf);
}
return ;
}
 

compile it and call it child_app

Now write the code to connect them:

#include<stdio.h>
#include<unistd.h> int main()
{
int arr[];
char argv[];
pipe(arr);
if(fork())
{
puts("starting parent");
close(arr[]);
sprintf(argv,"%d",arr[]);
execlp("./parent_app",argv,NULL);
}
else
{
puts("starting child");
close(arr[]);
sprintf(argv,"%d",arr[]);
execlp("./child_app",argv,NULL);
}
return ;
}

 关于 execlp用法:https://stackoverflow.com/questions/21558937/i-do-not-understand-how-execlp-works-in-linux

 

compile and run it (placing the previous executables in the same directory)

We create a pipe, on the parent we close the read fd and move the write fd as a parameter to the main , on the child we do the opposite

Note that in this design if we want to change the communication type to use unix domain socket (or any other object base on file descriptor) we only need to change and compile the last program.

上述章节摘抄自:https://devarea.com/linux-fork-system-call-and-its-pitfalls/

系统调用之fork()用法及陷阱的更多相关文章

  1. C++ std::map::erase用法及其陷阱

    1.引入: STL的map中有一个erase方法用来从一个map中删除制定的节点 eg: map<string,string> mapTest; typedef map<string ...

  2. python fork 用法

    import os import sys ips = ( "192.168.45.%s" % i for i in range(1,255)) for ip in ips: pid ...

  3. scanf 用法及陷阱(转)

    函数名: scanf 功 能: 执行格式化输入 用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设备( ...

  4. 【转】小心stringstream.str()字符串用法的陷阱

    --------------------- 作者:心中那自由的世界 来源:CSDN 原文:https://blog.csdn.net/119365374/article/details/7744678 ...

  5. fork系统调用(转载)

    (1) fork系统调用说明 fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程.fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返 ...

  6. [Linux]系统调用理解(3)

    本文介绍了Linux下的进程的一些概念,并着重讲解了与Linux进程管理相关的重要系统调用wait,waitpid和exec函数族,辅助一些例程说明了它们的特点和使用方法. 1.7 背景 在前面的文章 ...

  7. 系统调用wait、waitpid和exec函数

    本文介绍了Linux下的进程的一些概念,并着重讲解了与Linux进程管理相关的重要系统调用wait,waitpid和exec函数族,辅助一些例程说明了它们的特点和使用方法. 1.7 背景 在前面的文章 ...

  8. Linux系统调用(转载)

    目录: 1. Linux系统调用原理 2. 系统调用的实现 3. Linux系统调用分类及列表 4.系统调用.用户编程接口(API).系统命令和内核函数的关系 5. Linux系统调用实例 6. Li ...

  9. [转帖]Linux下fork函数及pthread函数的总结

    Linux下fork函数及pthread函数的总结 https://blog.csdn.net/wangdd_199326/article/details/76180514 fork Linux多进程 ...

随机推荐

  1. (转载)人脸识别中Softmax-based Loss的演化史

    人脸识别中Softmax-based Loss的演化史  旷视科技 近期,人脸识别研究领域的主要进展之一集中在了 Softmax Loss 的改进之上:在本文中,旷视研究院(上海)(MEGVII Re ...

  2. SDKMAN一个基于命令行界面的SDK用户环境管理程序

    1.背景 使用过Python开发的朋友,应该了解到Python2和Python3语法的差异,有时候从网上下载了基于不同解释器的代码,要来回切换版本, 使用起来不是很方便,有时候甚至很麻烦.于是有人发明 ...

  3. mavn jar包依赖冲突解决

    背景:使用maven很方便,但是引入冲突也很常见.后果很严重,各种不明实体找不到,所以需要对jar包的依赖有一个清晰的认识. 查看冲突 参考:用dependency:tree查看maven引入jar包 ...

  4. Gradle之dependenciens的各种依赖说明

    implementation:对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开.api 完全等同于compile指 ...

  5. SpringBoot加载自定义yml文件

    自定义配置文件(跟SpringBoot的application.yml同一目录下): nlu-parse-rule: title: "NLU响应结果解析规则" desc: &quo ...

  6. Go基础编程实践(五)—— 错误和日志

    自定义错误类型 Go中可以使用errors.New()创建错误信息,也可以通过创建自定义错误类型来满足需求.error是一个接口类型,所有实现该接口的类型都可以当作一个错误类型. // error类型 ...

  7. delphi 返回所有URL

    delphi 返回所有URL USER MSHTMLprocedure TfrmWebEmail.btn5Click(Sender: TObject);var Doc: IHTMLDocument2; ...

  8. pytest_05_fixture之conftest.py

    前面一篇讲到用例加setup和teardown可以实现在测试用例之前或之后加入一些操作,但这种是整个脚本全局生效的,如果我想实现以下场景: 用例1需要先登录,用例2不需要登录,用例3需要先登录.很显然 ...

  9. python3的pip3安装

    ---恢复内容开始--- pip3的安装需要对应一整套python的编译工具库,所以安装好的pip3是这个样子: inear@Ai:~$ pip3 -V pip 18.1 from /usr/lib/ ...

  10. 自己使用的jquery公用common.js

    /*解决ie8中js数组没有indexOf方法*/ jQuery.extend({ exportResport : function(url, method, params){ var paramCo ...