首页 > 系统相关 >Linux系统编程——进程标识、进程创建

Linux系统编程——进程标识、进程创建

时间:2024-10-16 18:17:00浏览次数:9  
标签:fork void 编程 pid Linux 进程 include ID

一、进程标识(pid)

  每个进程都有一个非负整数形式的唯一编号,即 PID。PID 在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其 PID 就可以为其它进程所用。进程的 PID 由系统内核根据延迟重用算法生成,以确保新进程的 PID 不同于最近终止进程的 PID。

1、特殊的进程标识

0 号进程,调度进程:通常是调度进程,常常被称为交换进程(swapper)。该进程是内核的一部分,所有进程的根进程,它并不执行任何磁盘上的程序,因此也被称为系统进程。
1 号进程,init进程:通常是 init 进程,在自举过程结束时由内核调用。
2号进程,页守护进程:负责虚拟内存系统的分页操作。

2、获取进程标识

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程 ID
pid_t getppid(void); 返回:调用进程的父进程 ID
uid_t getuid(void); 返回:调用进程的实际用户 ID
uid_t geteuid(void); 返回:调用进程的有效用户 ID
gid_t getgid(void); 返回:调用进程的实际组 ID
gid_t getegid(void); 返回:调用进程的有效组 ID
注意,这些函数都没有出错返回

二、进程创建(fork)和父子进程

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

1、函数功能

  fork()主要用于以复制正在调用进程的方式去创建一个新的进程,新进程叫做子进程,原来的进程叫做父进程。

2、与fork()相关的一些问题

(1)由 fork()创建的新进程被称为子进程。fork()函数调用一次,但返回两次。两次返回的区别是:子进程的返回值是 0,而父进程的返回值则是新建子进程的进程 ID。通过fork()的返回值来区别父子进程,如果出错返回-1。

(2)调用fork()前的代码只有父进程执行,fork()成功返回后的代码,父子进程都会执行。

(3)将新建子进程 ID 返回给父进程的理由:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的 ID。

(4)fork 使子进程得到返回值 0 的理由:一个进程只会有一个父进程,所以子进程总是可以调用 getppid 以获得其父进程的进程 ID(进程 ID 0 总是由内核交换进程使用,所以一个子进程的进程 ID 不可能为 0)。

(5)调用fork()出错的原因:系统中已经存在太多的进程;调用函数fork()的用户进程太多。

3、fork()写时复制

  子进程是父进程的不完全副本。子进程的数据区、bbs区、堆栈区(包括 I/O 流缓冲区),甚至参数和环境区都从父进程拷贝,唯有代码区与父进程共享。 因为,代码区是可执行指令 、字面值常量 、具有常属性且被 初始化的全局、静态全局 和 静态局部变量。
  传统的fork()系统调用直接把所有的资源复制给新创建的进程,这种实现过于简单并且效率低下。Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

4、文件共享

  在重定向父进程的标准输出时,子进程的标准输出也被重定向了。fork的一个特性是进程的所有打开文件描述符都被复制到子进程中。在 fork 之后处理文件描述符有以下两种常用的操作模式:

        1. 父进程等待子进程完成:在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已做了相应更新。
        2. 父进程和子进程各自执行不同的程序段:在这种情况下,在 fork 之后,父进程和子进程各自关闭它们不需使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程经常使用的。

5、父进程和子进程之间的区别

  • fork 的返回值不同,子进程返回 0, 而父进程返回新建子进程 ID。
  • 进程 ID 不同。
  • 这两个进程的父进程 ID 不同:子进程的父进程 ID 是创建它的进程的 ID,而父进程的父进程 ID 则不变。
  • 子进程的tms_utime , tms_stime , tms_cutime以及tms_ustime设置为0。
  • 子进程不继承父进程设置的文件锁。
  • 子进程的未处理闹钟被清除。
  • 子进程的未处理信号集设置为空集。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    // fork函数被调用一次,但返回两次;在父进程中返回子进程的pid,子进程返回0
    pid = fork(); // 产生父子进程
    if (pid > 0) // 父进程执行代码段
    {
        while(1)
        {
            printf("I am parent\n");
            printf("my pid = %d",getpid());
            printf("my parent pid = %d",getpid());
            sleep(1);
        }
    }
    else if (pid == 0) // 子进程执行代码段
    {
        while(1)
        {
            printf("I am child\n");
            printf("my pid = %d",getpid());
            printf("my parent pid = %d",getppid());
            sleep(3);
        }
    }
    else
    {
        perror("fork"); //打印出错信息
        exit(1);
    }
    return 0;
}

三、fork(),vfork()和clone()的区别

1、进程四要素

  • 有一段程序供其执行(不一定是一个进程所专有的),就像一场戏必须有自己的剧本。
  • 有自己的专用系统堆栈空间(私有财产)。
  • 有进程控制块(task_struct)(“有身份证,PID”)。
  • 有独立的存储空间。

缺少第四条的称为线程,如果完全没有用户空间称为内核线程,共享用户空间的称为用户线程。

2、fork()

  fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。

3、vfork()

  vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。

  vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少第四个要素:独立的内存资源。

  另外由vfork创建的子进程要先于父进程执行,子进程执行时,父进程处于挂起状态,子进程执行完,唤醒父进程。除非子进程exit或者execve才会唤起父进程。

4、clone()

  clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

  clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。

四、孤儿进程与僵尸进程

1、父进程和子进程的关系

一个父进程可以创建多个子进程,但每个子进程最多只能有一个父进程。整个系统中只有一个根进程,即 PID 为 0  的调度进程。系统中的所有进程构成了一棵以调度进程为根的进程树。

2、父进程和子进程之间的区别

fork 的返回值不同,子进程返回 0, 而父进程返回新建子进程 ID。
进程 ID 不同
这两个进程的父进程 ID 不同:子进程的父进程 ID 是创建它的进程的 ID,而父进程的父进程 ID 则不变。
子进程的tms_utime , tms_stime , tms_cutime以及tms_ustime设置为0。
子进程不继承父进程设置的文件锁。
子进程的未处理闹钟被清除。
子进程的未处理信号集设置为空集。

3、孤儿进程

  父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行。如果父进程先于子进程终止,子进程即成为孤儿进程,同时被 init 进程收养,即成为 init 进程的子进程,因此 inti 进程又被成为孤儿院进程。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main (void)
{
    pid_t pid;
    if ((pid = fork ()) < 0)
        perror ("fork"), exit (1);
    else if (pid == 0)
    {
        sleep (3); // 子进程被暂停三秒,所以当父进程退出后子进程仍然未退出。
        printf ("这是子进程 pid = %d", getpid ());
        printf ("父进程的 ppid = %d\n",  getppid ());
    }
    else 
    {
        printf ("这是父进程 ppid = %d\n", getpid ());
    }
    return 0;
}

4、僵尸进程

  如果子进程先于父进程终止,但父进程由于某种原因,没有回收子进程的退出状态,子进程即成为僵尸进程。
  僵尸进程虽然已经不再活动,但其终止状态仍然保留,也会占用系统资源,直到被其父进程回收才得以释放。
  如果父进程直到终止都未回收它的已成僵尸的子进程,init 进程会立即收养并回收这些处于僵尸状态的子进程,因此一个进程不可能既是孤儿进程同时又是僵尸进程。
  一个进程成为僵尸进程需要引起注意,如果它的父进程长期运行而不终止,僵尸进程所占用的资源将长期得不到释放。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main (void)
{
    pid_t pid;
    pid = fork ();
    if  (pid == -1)
        perror ("fail to fork"), exit (1);
    else if (pid == 0)
    {
        printf ("这是子进程 pid = %d", getpid ());
        printf ("父进程的 ppid = %d\n",  getppid ());
    }
    else
    {
        // 父进程休眠了十秒,而在这期间,子进程已经退出了,
        //子进程就形成了一段时间的僵尸进程。
        sleep (10); 
        printf ("这是父进程 ppid = %d\n", getpid ());
    }
    return 0;
}

标签:fork,void,编程,pid,Linux,进程,include,ID
From: https://blog.csdn.net/qq_74811378/article/details/142987806

相关文章

  • 关于Linux shell的简单分享
    背景前两天看到有人分享说,谷歌程序员至少要会3种语言,这要求倒也不过分。一个强类型、一个弱类型,一个工具型语言,刚好三个。想着随便更新点东西,就写几点关于工具shell语言的经验分享。在安装、部署、扩容、升级等重要场景中,Bash的方便性不言而喻。但我始终提醒大家,对代码保持敬......
  • Linux多进程通信--管道、消息队列、共享内存
    转载至https://www.cnblogs.com/LUO77/p/5816326.html多进程:首先,先来讲一下fork之后,发生了什么事情。由fork创建的新进程被称为子进程(childprocess)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程id。将子进程id返......
  • linux 为普通用户配置sudo权限
    linux为普通用户配置sudo权限  目录一.关于sudo二.sudo的工作过程三.为普通用户配置sudo权限3.1方法一:把普通用户的附属组更改为wheel,使其具有sudo权限(推荐)3.2方法二:修改/etc/sudoers文件,使普通用户具有sudo权限四./etc/sudoers配置文件详解 一.关于sud......
  • openEuler、Linux操作系统---(7)权限管理与进程管理讲解
    一、引言在Linux操作系统的广袤领域中,权限管理与进程管理犹如两道坚实的壁垒,对系统的安全性、稳定性及运行效率起着至关重要的作用。本次讲解将引领各位深入探索Linux系统的权限机制和进程管理领域,通过对chmod、chown、umask等命令的运用来实现文件权限的修改、文件所有......
  • OS-Lab3-Linux进程相关的系统调用和线程相关的库函数
    实验目的1) 演示/实践:Linux进程控制相关的系统调用(如exec()/exit()等)2) 演示/实践:Linux多进程的条件竞争/RaceCondition的现象观察3) 演示/实践:Linux的线程库pthread的初步和入门(如何创建一个用户态的Linux线程:相关库函数pthread_create()等),以及其条件竞争/RaceCondition的现象......
  • OS-Lab4-多线程编程基础
    实验目的事先编辑好数据文件1.dat和2.dat,它们的内容分别为12345678910和-1-2-3-4-5-6-7-8-9-10。根据示例代码和其运行效果设计一个程序,在这个程序中一共有3个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另外一个线程从缓冲区读取数据作不同的处理(加和......
  • Linux基础篇
    liunx基础篇操作系统OperatingSystem简称OS,是软件的一部分,它是硬件基础上的第一层软件,是硬件和其它软件沟通的桥梁。操作系统会控制其他程序运行,管理系统资源,提供最基本的计算功能,如管理及配置内存、决定系统资源供需的优先次序等,同时还提供一些基本的服务程序。1、描......
  • OS-Lab2-Linux进程控制相关命令和系统调用(含gcc编程)
    实验目的1) 概念:Linux的gcc编译器2) 演示/实践:gcc编译器的初步和入门(编写一个带printf的while程序)3) 演示/实践:Linux进程控制相关的命令(如ps/pstree/top/kill/等)演示/实践:Linux进程控制相关的系统调用(如fork()/getpid()等)4) 概念:Linux的gdb调试器5) 概念:Linux的make工具及其Makefi......
  • OS-Lab1-Linux的常见命令和Shell脚本编程
    Lab1:Linux常见命令的基本使用和总结:如ls、cd、mkdir、torch、rm、mv、clear、grep、find、cat、shutdown等。Lab2:Linux常见命令的应用案例:下面的步骤,通过相关的Linux命令,完成指定步骤的操作Lab3:VMware的Host(如Windows系统)与Guest(如UbuntuLinux)的数据交换:常见......
  • Linux环境搭建vsftpd服务
    vsftpd(verysecureFTPdaemon)是一个广泛使用的、开源的FTP服务器软件,以其高性能、高安全性和稳定性著称,支持多种FTP相关协议,包括FTP、SFTP(通过SSH)、TLS/SSL加密的FTP等。一:安装vsftpd#安装vsftpd服务yuminstallvsftpd-y#启动FTP服务,并设置开机自启动systemctlenablev......