• linux进程学习-创建新进程


    init进程将系统启动后,init将成为此后所有进程的祖先,此后的进程都是直接或间接从init进程“复制”而来。完成该“复制”功能的函数有fork()和clone()等。
    
     
    
    一个进程(父进程)调用fork()函数后将会把自己复制一份,而这个被复制出来的新进程称为子进程,就这么简单地完成了新进程的创建。fork函数几乎完整地复制了父进程,除了几个特殊的方面外(至少pid不一样吧,具体的查看这里有一个完整的列表)
    
    fork函数原型:pid_t fork(void);
    
    其包含在 unistd.h 头文件中,其中pid_t是表示“type of process id”的32位整数, 至于函数的返回值,取决于在哪个进程中来检测该值,如果是在新创建的进程中,其为0;如果是在父进程中(创建新进程的进程),其为新创建的进程的id; 如果创建失败,则返回负值。
    
    #include <stdio.h>
    
    #include <unistd.h>
    
    
    
    
    int main ()
    
    {
    
         printf("app start...
    ");
    
         
    
         pid_t id = fork();
    
         
    
         if (id<0) {
    
             printf("error
    ");
    
         }else if (id==0) {
    
             printf("hi, i'm in new process, my id is %d 
    ", getpid());
    
         }else {
    
             printf("hi, i'm in old process, the return value is %d
    ", id);
    
         }
    
         
    
         return 0;
    
     }
    
    (上面使用了getpid函数,其返回当前进程的pid。)
    
    程序输出为:
    
     app start...
    
     hi, i'm in old process, the return value is 5429
    
     hi, i'm in new process, my id is 5429
    
     
    
    新进程被创建后将被放入“可执行队列”而进入“TASK_RUNNING”状态,并从父进程当前位置独立地运行。看下面的DEMO:
    
    #include <stdio.h>
    
    #include <unistd.h>
    
     
    
    int main ()
    
    {
    
         printf("app start...
    ");
    
         
    
         int counter = 0;
    
         
    
         fork();
    
         
    
         counter++;
    
         
    
         printf("the counter value %d
    ", counter);
    
         
    
         return 0;
    
     }
    
    输出为:
    
    app start...
    
    the counter value 1
    
    the counter value 1
    
    画个图就很能容易解释了:
    
     
    
    新进程得到的是父进程的副本,所以,父子进程counter变量不会相互影响
    
     
    
    fork函数将复制父进程的地址空间给子进程,但为了提高效率,复制过程并不会真正的进行物理内存的完整复制,而是采用“写拷贝(copy-on-write)”技术让父子进程尽可能地长久地共享该物理内存,仅仅是复制内存页入口地址并标记写拷贝对应的页面,当修改真正发生时才真正复制。
    
     
    
    再来一个demo:
    
    #include <stdio.h>
    
    #include <unistd.h>
    
     
    
    int main ()
    
    {
    
         printf("app start...");
    
         
    
         fork();
    
         
    
         return 0;
    
     }
    
    输出为:
    
    app start...app start...
    
    好奇怪是吧?情况是这样的:
    
    当你调用printf时,字符串被写入stdout缓冲区(还没刷到屏幕上),然后fork,子进程复制了父进程的缓冲区,所以子进程的stdout缓冲区中也包含了“app start ...”这个字符串,然后父子进程各自运行,当他们遇到return语句时,缓冲器会被强制刷新,然后就分别将“app start...”刷到了屏幕上。如果想避免,在fork前,调用fflush强制刷新下缓冲区就可以了,在字符串后面加上“
    ”也可以,因为stdout是按行缓冲的。更多的,参考我的一篇博文。
    
     
    
    与fork()函数非常类似的还有一个交vfork()的函数,它需要一点exec的知识,可以先阅读完“运行新程序”后再回头来看。
    
    我们知道,调用fork函数后,新进程会复制父进程的内存空间并继续运行,也就是说子进程仍然运行着和父进程相同的程序代码,在大多数情况下这并非我们的本意,我们一般会fork一个新的进程,然后调用exec族函数(族函数表示由几个功能类似的函数组成的一组函数)来运行新的程序,exec族函数会用新的进程映像重写原复制过来的内存空间,比如用新程序的代码去覆盖原来代码段的内容等。很明显,fork时的复制工作白干了。所以,在这种情形下(fork后立即exec),一个聪明的做法是,fork时不复制父进程的内存空间而是共享(占用)父进程的内存空间以暂时在父进程的内存空间内运行(类似于线程),等到调用exec族函数后便拥有了自己的内存空间,然后脱离父进程独立运行,而这正是vfork所做的事情,也是vfork和fork的主要区别。
    
    在调用exec前,由于子进程共享了父进程的内存空间,如果子进程篡改父进程数据结果将不可预期,而这是不允许的,同时,父进程会等待子进程调用exec函数后(或子进程调用_exit()退出,比如exec失败时)才继续运行。如果不按此约定编写代码则可能会引起死锁或其它不可。预期的情况。
    
     
    
    还有一个相对较复杂的方法来创建新进程:clone()。我们知道fork()创建的进程和父进程是独立的,两者之间没有干扰,并且需要专门的“进程间通讯(IPC)”机制来进行沟通。其实,在某些情况之下我们并不希望父子进程之间显得那么独立,因为那可能带来更多的通讯成本和资源复制带来的浪费。clone()这个系统调用便允许我们选择性地继承(共享)父进程资源,比如我们共享父进程的内存空间的话,那么创造出来的新进程实际上就是一个线程了。
    
     
    
    clone 函数:
    
    int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... );
    
    第1个参数是一个函数指针,表示新进程要做的工作。
    
    第2个参数指向新进程所需的堆栈空间。
    
    第3个参数,指示子进程如何与父进程共享资源,可选项很多,请参考这里。但一些常用的如下所示:
    
    Ÿ CLONE_SIGHAND   子进程与父进程共享相同的信号处理(signal handler)表。
    
    Ÿ CLONE_VM        子进程与父进程运行于相同的内存空间。
    
    Ÿ CLONE_FILES      子进程与父进程共享相同的文件描述符(file descriptor)表
    
    Ÿ CLONE_FS         子进程与父进程共享相同的文件系统,包括root、当前目录、umask。
    
    Ÿ CLONE_PARENT    创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
    
    另外,为了更好的理解fork()和clone()的关系:
    
    fork的实现:
    
         do_fork(CLONE_SIGCHLD,...)
    
    clone的实现:
    
         do_fork(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGCHLD,...)
  • 相关阅读:
    ASP.NET 使用 X509Certificate2 系统找不到指定的文件
    SQL2000中TOP后不能使用变量
    补丁生成与应用工具 V1.5.4
    检测到通信错误。正在使用的通信协议:"TCP/IP"。正在使用的通信API:"SOCKETS"。检测到错误的位置:""。检测到错误的通信函数:"gethostbyname"。协议特定的错误代码:"*"、"11004"、"*"。 SQLST
    CLR 无法从 COM 上下文 0x1a2740 转换为 COM 上下文 0x1a28b0,这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长的操作
    Chart 控件 for vs2008的安装
    SqlServer孤立用户解决——"因为该用户存在对象,所以不能删除该用户。"
    函数 replace 的参数 1 的数据类型 text 无效。
    安装SqlServer2000出现"有挂起的操作"提示的解决
    DB2、ORACLE SQL写法的主要区别
  • 原文地址:https://www.cnblogs.com/zendu/p/4980599.html
Copyright © 2020-2023  润新知