《Linux C编程实战》笔记:进程操作之创建进程

发布时间:2023年12月19日

进程是一个动态的实体,是程序的一次执行过程。进程是操作系统资源分配的基本单位。

以下是一些概念,我就直接抄书了

进程是操作系统的知识,简单理解的话,你写的代码运行起来算一个进程?

创建进程

每个进程由进程ID号标识,进程被创建时系统会为其分配一个惟一的进程ID。当一个进程向其父进程(创建该进程的进程)传递其终止消息时,意味这个进程的整个生命周期的结束。此时,该进程占用的所有资源包括进程ID被全部释放。
创建进程有两种方式,一是由操作系统创建;二是由父进程创建。由操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系。而对于由父进程创建的进程(通常称为子进程),它们和父进程存在隶属关系。子进程又可以创建进程,这样形成一个进程家族。子进程可以继承其父进程几乎所有的资源。在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。
系统调用fork是创建一个新进程的惟一方法(创建一个进程通常也称为fork一个进程),除了极少数以特殊方式创建的进程,如 init进程,它是内核启动时以特殊方式创建的。进程调用fork函数就创建了一个子进程。
创建了一个子进程之后,父进程和子进程争夺CPU,抢到CPU 者执行,另外一个挂起等待。如果想要父进程等待子进程执行完毕以后再继续执行,可以在 fork 操作之后调用 wait或waitpid。一个刚刚被fork的子进程会和它的父进程一样,继续执行当前的程序。几个进程同时执行一个应用程序通常用处不大。更常见的使用方法是子进程在被fork后可以通过调用exec函数执行其他程序

fork函数

#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);

一般情况下,函数最多有一个返回值,但fork函数非常特殊,它有两个返回值,即调用一次返回两次。成功调用fork函数后,当前进程实际上已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程。父子进程在调用fork函数的地方分开,fork函数有两个返回值,一个是父进程调用fork函数后的返回值,该返回值是刚刚创建的子进程的ID;另一个是子进程中fork函数的返回值,该返回值是0。fork函数返回两次的前提是进程创建成功,如果进程创建失败,则只返回-1。两次返回不同的值,子进程返回值为0,而父进程的返回值为新创建的子进程的进程ID,这样可以用返回值来区别父、子进程。

示例程序1

演示fork函数的用法

#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;

int main(){
    pid_t pid;
    printf("Process creation study\n");
    pid=fork();//创建子进程
    switch (pid)
    {
    case 0://子进程
        printf("Child process is running,CurPid is %d,ParentPid is %d\n",pid,getppid());
        break;
    case -1://创建失败
        perror("Process creation failed\n");
        break;
    default://父进程
        printf("Parent process is running,ChildPid is %d,ParentPid is %d\n",pid,getpid());
        break;
    }
    exit(0);
}

运行结果

可以看到,fork在父子进程的返回值不同。程序在fork执行完后在此处创建一个子进程,父子进程会分别一起执行fork之后的语句。

结果里是父进程先于子进程执行,但一般来说是不固定的

示例程序2

为了演示这种不固定,可以看以下示例程序

#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;

int main(){
    pid_t pid;
    const char *msg;
    int k;
    printf("Process creation study\n");
    pid=fork();
    switch (pid)
    {
    case 0:
        msg="Child process is running\n";//子进程会输出的字符串
        k=3;
        break;
    case -1:
        perror("Process creation failed\n");
        break;
    default:
        msg="Parent process is running\n";//父进程会输出的字符串
        k=5;
        break;
    }
    while (k>0)
    {
        fputs(msg,stdout);
        sleep(1);
        k--;//子进程和父进程的k不相同
    }
    
    exit(0);
}

运行结果:

可以看出父子进程交替执行

sleep() 函数是一个标准C库函数,用于让程序暂停执行指定的时间,以秒为单位。在Linux/Unix系统中,它的声明在头文件 <unistd.h> 中。

前面提到 fork在创建进程失败时,返回-1。失败的原因通常是父进程拥有的子进程的个数超过了规定的限制,此时errno值为EAGAIN。如果可供使用的内存不足也会导致进程创建失败,此时errno值为 ENOMEM

子进程会继承父进程的很多属性,主要包括用户ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字、信号屏蔽字、上下文环境、共享的存储段、资源限制等。子进程与父进程有一些不同的属性,主要有如下这些。

  1. 子进程有它自己惟一的进程ID。
  2. fork的返回值不同,父进程返回子进程的ID,子进程的则为0。
  3. 不同的父进程ID。子进程的受进程D为创建它的父进程ID。
  4. 子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。
  5. 子进程不继承父进程设置的文件锁。
  6. 子进程不继承父进程设置的警告。
  7. 子进程的未决信号集被清空。

示例程序3

本示例演示孤儿进程

如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由 init进程收养,成为init进程的子进程。

#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
int main(){
    pid_t pid;
    pid=fork();
    switch (pid)
    {
    case 0://子进程进入一个死循环,一直不会结束
        while (1)
        {
            printf("A background process,PID:%d\n,ParentID:%d\n",getpid(),getppid());
            sleep(3);
        }
        
    case -1:
        perror("Process creation failed\n");
        exit(-1);
    default://父进程先结束
        printf("I am parent process,my pid is %d\n",getpid());
        exit(0);
    }   
    return 0;
}

这我就不运行了,子进程根本结束不了,预期的运行结果是这样的

父进程打印一次就执行完毕了,此后子进程就成了孤儿进程,由init进程收养,可以看到此时子进程的父进程ID变为1.

vfork函数

vfork也可以用来创建新进程,和fork相比,他有独特用处。

  1. 返回值:

    • fork() 的返回值是新创建的子进程的进程ID(PID)在父进程中,而在子进程中返回0。因此,通过返回值的不同可以在父进程和子进程中采取不同的操作。
    • vfork() 的返回值也是新创建的子进程的PID,但与fork() 不同的是,父进程和子进程共享地址空间,所以在子进程中对地址空间的修改会影响到父进程。
  2. 地址空间:

    • fork() 中,子进程获得父进程的完整地址空间的副本,父子进程互不干扰,各自独立执行。
    • vfork() 中,子进程共享父进程的地址空间,子进程运行在父进程的地址空间中,直到调用 exec 函数或者 exit()
  3. 执行时机:

    • fork() 中,父进程和子进程之间的执行是并发的,即它们可以同时运行。
    • vfork() 中,父进程会被挂起,直到子进程调用 exec 函数或者 exit(),因为子进程共享父进程的地址空间,如果父进程继续执行可能会影响到子进程的执行。
  4. 用途:

    • fork() 适用于创建一个独立的进程,父子进程各自执行,相互不影响。通常用于多任务并发执行。
    • vfork() 主要设计用于在子进程中立即执行一个新程序,它更加轻量级,因为不需要复制整个地址空间,但是需要小心使用,以避免父子进程之间的竞态条件。

总体而言,fork() 适用于大多数常规情况,而 vfork() 更适用于特殊情况,例如在子进程中立即执行一个新程序,而不涉及大量的地址空间复制。

简单来说,vfork创建的子进程会先执行,执行完毕父进程才能继续,而且子进程对变量的修改父进程可见且受影响,因为它们共享地址空间。

使用vfork需谨慎。

示例程序4

下面例子说明两者的不同

#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
int globVar=5;//全局变量

int main(){
    pid_t pid;
    int var=1,i;
    pid=fork();
    /*pid=vfork();*/
    switch (pid)
    {
    case 0:
        i=3;
        while (i-->0)
        {
            printf("Child process is running\n");
            globVar++;
            var++;
            sleep(1);
        }
        printf("Child's globVar =%d,var=%d\n",globVar,var);
        break;
    case -1:
        perror("Process creation failed\n");
        exit(-1);
    default:
        i=5;
        while (i-->0)
        {
            printf("Parent process is running\n");
            globVar++;
            var++;
            sleep(1);
        }
        printf("Parent's globVar =%d,var=%d\n",globVar,var);
        break;
    }   
    exit(0);
}

首先我们运行fork版本的

可以看到子进程和父进程不管是全局的globvar还是局部的var都增加了对应的值,证明子父进程地址空间是独立的。而且子父进程的执行顺序不确定。

下面我们把fork注释,把vfork取消注释,再次运行

可以看到父进程结束时的var的值均是在子进程结束时的基础上加的5,证明它们共享地址空间。而且子进程执行完毕后父进程才能继续执行。

书内还有守护进程的内容,不过感觉 跨度有点大,就先跳过了

文章来源:https://blog.csdn.net/ouliten/article/details/135056493
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。