zoukankan      html  css  js  c++  java
  • 第6章 进程控制(2)_创建进程

    4. 创建进程

    4.1 fork、vfork函数

    (1)函数原型

    头文件

    #include<unistd.h>

    #include<sys/types.h>

    函数

    pid_t fork(void);

    pid_t vfork(void)

    返回值

    子进程中为0,父进程中为子进程ID,出错为-1

    功能

    创建子进程

    备注

    (1)fork创建的新进程被称为子进程,该函数被调用一次,但返回两次。两次返回的区别是在子进程中的返回值为0,而在父进程的返回值为新子进程的进程ID

    (2)创建子进程,父子进程哪个先运行是根据系统调度且复制父进程的内存空间(数据空间、堆、栈)。

    (3)vfork创建子进程,但子进程先运行且不复制父进程的内存空间

    【编程实验】父子进程交替输出

    //process_fork.c

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char* argv[])
    {
        printf("pid: %d
    ", getpid());//只有父进程会执行,子进程不执行。
    
        pid_t pid;
        pid = fork();  //创建子进程,调用该函数这后会出现父子两个进程。其中
                       //父进程调用fork后,返回子进程的ID。由于子进程复制了
                       //父进程的内存空间和寄存器状态。因此也会复制父进程的
                       //EIP,然后从EIP所指位置开始执行,但函数的返回值为0.
       
        //fork之后会运行两个进程(父进程和子进程)
        if(pid < 0){
            perror("fork error");
            exit(1);
        }else if (pid > 0){
            //父进程(在父进程中fork返回的是子进程的ID)
            printf("I am parent process, my pid is %d, ppid is %d, fork return value is %d
    ", getpid(), getppid(), pid);
            
            int i = 0;
            for(i=0; i<10; i++){
                printf("%d: This is a parent process pid is: %d
    ",i + 1, getpid());
                sleep(1);
            }
    
        }else{
            //子进程(在子进程中fork返回的是0)
            printf("I am child process, my pid is %d, ppid is %d, fork return value is %d
    ", getpid(), getppid(), pid);
            
            int i = 0;
            for(i=0; i<10; i++){
                printf("%d: This is a child process pid is: %d
    ",i + 1, getpid());
                sleep(1);
            }
        }
    
    
        printf("pid: %d
    ", getpid()); //父子进程都会执行到这里!
        sleep(1);
        exit(0);
    
        return 0;
    }

    4.2 子进程的继承

    (1)子进程的继承属性(即从父进程复制一份过来

     

      ①用户信息和权限、目录信息、信号信息、环境、资源限制。

      ②共享存储段、共享代码段(注意:代码段只有一份)、堆、栈和数据段、存储映射。

    【编程实验】父子进程的复制(数据段、堆栈)

    //process_fork2.c

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int g_v = 30;  //注意:是个全局变量
    
    int main(int argc, char* argv[])
    {
        int a_v = 30;
        static int s_v = 30;
    
        printf("pid: %d
    ", getpid());//只有父进程会执行,子进程不执行。
    
        pid_t pid;
        pid = fork();  //创建子进程
        
        //fork之后会运行两个进程(父进程和子进程)
        if(pid < 0){
            perror("fork error");
            exit(1);
        }else if (pid > 0){
            //父进程(在父进程中fork返回的是子进程的ID)
            printf("I am parent process, my pid is %d, ppid is %d, fork return value is %d
    ", getpid(), getppid(), pid);
            
            g_v = 50; a_v =50; s_v = 50;
            //打印各种变量的地址
            printf("g_v, %p, a_v: %p, s_v: %p
    ", &g_v, &a_v, &s_v);        
    
        }else{
            //子进程(在子进程中fork返回的是0)
            printf("I am child process, my pid is %d, ppid is %d, fork return value is %d
    ", getpid(), getppid(), pid);
            
            g_v = 40; a_v = 40; s_v = 40;
            //打印各种变量的地址
            printf("g_v, %p, a_v: %p, s_v: %p
    ", &g_v, &a_v, &s_v);        
        }
    
        //打印各类变量的值
        printf("pid: %d, g_v: %d, a_v: %d, s_v: %d
    ", getpid(), g_v, a_v, s_v); //父子进程都会执行到这里!
        sleep(1);
        exit(0);
    
        return 0;
    }
    /*输出结果:(注意,父子进程中变量虚拟地址一样,但存放物理内存不同!)
     pid: 1586
     I am parent process, my pid is 1586, ppid is 1476, fork return value is 1587
     g_v, 0x80499bc, a_v: 0xbf9f2928, s_v: 0x80499c0
     pid: 1586, g_v: 50, a_v: 50, s_v: 50
     I am child process, my pid is 1587, ppid is 1586, fork return value is 0
     g_v, 0x80499bc, a_v: 0xbf9f2928, s_v: 0x80499c0
     pid: 1587, g_v: 40, a_v: 40, s_v: 40
     */

    (2)子进程特有的属性

      ①进程ID、锁信息、运行时间、未决信息

    (3)操作文件时的内核结构变化

     

      ①子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node。实际上fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共一个文件表项。这种共享方式使父、子进程对同一文件使用了同一个文件偏移量

      ②父进程创建一个子进程后,文件表项中的引用计数加1当父进程close操作后,计数减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。

    【编程实验】文件共享1(父子进程同写一个文件)

    //process_fork3.c

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main(int argc, char* argv[])
    {
        FILE* fp = fopen("s.txt", "w"); //标准C库函数方式
        int fd = open("s_fd.txt", O_WRONLY | O_CREAT | O_TRUNC, 
                              S_IRWXU | S_IRWXG); //系统调用方式
    
        char* s = "hello world!";
        ssize_t size = strlen(s) * sizeof(char);
        
        /*注意:以下写入文件操作是在fork调用这前*/
    
        //标准IO函数(带缓存),当fork子进程后,缓存也会被复制给
        //子进程,所以父子进程当前的缓存内存内容都是一样的,即
        //会写入同样的一段内容。
        fprintf(fp, "s: %s, pid: %d", s, getpid()); //写入缓存 
        //内核提供的IO系统调用(不带缓存)
        write(fd, s, size);  //不带缓存,直接写入文件。
    
        pid_t pid;
        pid = fork();  //创建子进程
        
        //fork之后会运行两个进程(父进程和子进程)
        if(pid < 0){
            perror("fork error");
            exit(1);
        }else if (pid > 0){//父进程(在父进程中fork返回的是子进程的ID)
            //父子进程的fd指向同一个文件表项,共享文件偏移量
            char* context = "
    I come from parent process!";
            write(fd, context, strlen(context)* sizeof(char));
    
        }else{ //子进程(在子进程中fork返回的是0)
            char* context = "
    I come from child process!";
            //父子进程的fd指向同一个文件表项,共享文件偏移量
            write(fd, context, strlen(context)* sizeof(char)); 
        }
    
        //父子进程都要执行,由于带缓存,会将之前的内容(相同的)连同以下的
        //内容(不同的)写入文件中。
        fprintf(fp, " pid: %d
    ", getpid());
        fclose(fp);
        close(fd);
    
        sleep(1);
    
        exit(0);
    
        return 0;
    }
    /*输出结果:(注意,父子进程中变量虚拟地址一样,但存放物理内存不同!)
    s.txt的内容:
    : hello world!, pid: 1654 pid: 1654
    s: hello world!, pid: 1654 pid: 1655
    
     s_fd.txt的内容:
     world!I come from parent process!
     I come from child process!
     */

    【编程实验】文件共享2(父进程调整文件偏移量,子进程写文件)

    //process_append.c

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main(int argc, char* argv[])
    {
        if(argc < 2) {
            fprintf(stderr, "usage: %s file
    ", argv[0]);
            exit(1);
        }
    
        int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC);
        if(fd < 2){
            perror("open error");
            exit(1);
        }
    
        pid_t pid = fork(); //创建子进程
        if(pid < 0){
            perror("fork error");
            exit(1);
        }else if (pid > 0) { //parent process
            //父进程将文件偏移量调整到文件尾部
            if(lseek(fd, 0L, SEEK_END) < 0){
                perror("lseek error");
                exit(1);
            }
        }else{ //child process
            //子进程从文件尾部追加内容
            char* str = "hello child
    ";
            ssize_t size = strlen(str)* sizeof(char);
            
            sleep(3);  //休眠,等待父进程调整文件偏移量
            //此处的fd是从父进程中复制而来,与父进程的fd指向同一个文件表项
            if(write(fd, str, size) != size){
                perror("write error");
                exit(1);
            }
        }
    
        printf("pid: %d finish!
    ", getpid());
       
        close(fd); //父子进程都会执行该行,分别将文件引用计数器减1
    
        sleep(1);
    
        return 0;
    }

    4.3 fork的用法

    (1)一个父进程希望复制自己,使父子同时执行不同的代码段。这在网络服务进程中最常见——父进程等待客户端的服务请求。当请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求的到达。

    (2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

  • 相关阅读:
    poi隐藏列
    凯西太太的果园
    java中不可变对象深入理解
    excel添加空白行的快捷键
    如何在多个页面中,引入一个公共组件
    对后端返回的数据进行适配
    我与时间管理的故事
    在前端团队的那些日子(初见)
    我是这样做时间管理的(下)
    我是这样做时间管理的(上)
  • 原文地址:https://www.cnblogs.com/5iedu/p/6357552.html
Copyright © 2011-2022 走看看