第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: %dn", 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 %dn", getpid(), getppid(), pid);

        int i = 0;
        for(i=0; i<10; i++){
            printf("%d: This is a parent process pid is: %dn",i + 1, getpid());
            sleep(1);
        }

    }else{
        //子进程(在子进程中fork返回的是0)
        printf("I am child process, my pid is %d, ppid is %d, fork return value is %dn", getpid(), getppid(), pid);

        int i = 0;
        for(i=0; i<10; i++){
            printf("%d: This is a child process pid is: %dn",i + 1, getpid());
            sleep(1);
        }
    }


    printf("pid: %dn", getpid()); //父子进程都会执行到这里!
    sleep(1);
    exit(0);

    return 0;
}

4.2 子进程的继承

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

第6章 进程控制(2)_创建进程 

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

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

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

//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: %dn", 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 %dn", getpid(), getppid(), pid);

        g_v = 50; a_v =50; s_v = 50;
        //打印各种变量的地址
        printf("g_v, %p, a_v: %p, s_v: %pn", &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 %dn", getpid(), getppid(), pid);

        g_v = 40; a_v = 40; s_v = 40;
        //打印各种变量的地址
        printf("g_v, %p, a_v: %p, s_v: %pn", &g_v, &a_v, &s_v);        
    }

    //打印各类变量的值
    printf("pid: %d, g_v: %d, a_v: %d, s_v: %dn", 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)操作文件时的内核结构变化

第6章 进程控制(2)_创建进程 

  ①子进程只继承父进程的文件描述表,不继承但共享文件表项和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 = "nI come from parent process!";
        write(fd, context, strlen(context)* sizeof(char));

    }else{ //子进程(在子进程中fork返回的是0)
        char* context = "nI come from child process!";
        //父子进程的fd指向同一个文件表项,共享文件偏移量
        write(fd, context, strlen(context)* sizeof(char)); 
    }

    //父子进程都要执行,由于带缓存,会将之前的内容(相同的)连同以下的
    //内容(不同的)写入文件中。
    fprintf(fp, " pid: %dn", 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 filen", 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 childn";
        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!n", getpid());

    close(fd); //父子进程都会执行该行,分别将文件引用计数器减1

    sleep(1);

    return 0;
}

4.3 fork的用法

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

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

原文链接: https://www.cnblogs.com/5iedu/p/6357552.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第6章 进程控制(2)_创建进程

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/393648

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午3:17
下一篇 2023年4月3日 下午3:17

相关推荐