`

进程标识符操作函数

阅读更多
    每个进程都有一个非负整型表示的唯一进程 ID。虽说是唯一的,但进程 ID 是可复用的,当一个进程终止时,其进程 ID 就成为复用的候选者。多数 UNIX 系统使用延迟复用算法,使得赋予新建进程的 ID 不同于最近终止进程的 ID,以免将新进程误认为是使用同一 ID 的某个已终止的先前进程。
    系统中有一些专用进程,但具体细节随实现而不同。ID 为 0 的进程通常是调度进程,常常被称为交换进程。该进程是内核的一部分,并不执行任何磁盘上的程序,因此也被称为系统进程。进程 ID 1 通常是 init 进程(在 Mac OS X 10.4 中是 launchd 进程),在自举过程结束时由内核调用,以启动一个 UNIX 系统。该进程的程序文件一般是 /etc/init 或 /sbin/init,它通常读取与系统有关的初始化文件,如 /etc/rc* 文件、/etc/inittab 文件 和 /etc/init.d 中的文件等,并将系统引导到一个状态(如多用户)。init 进程不会终止,它是一个普通的用户进程而非内核中的系统进程,但它是以超级用户特权运行的。此外,每个 UNIX 系统实现都有它自己的一套提供操作系统服务的内核进程,例如,在某些 UNIX 的虚拟存储器实现中,进程 ID 2 是页守护进程,负责支持虚拟存储器系统的分页操作。
    除了进程 ID,每个进程还有其他一些标识符。下列函数可返回这些标识符(它们都没有出错返回)。
#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 函数创建一个新的子进程(某些平台提供了 fork 的几种变体,比如 vfork 以及 Linux 3.2.0 提供的 clone 系统调用,它允许调用者控制哪些部分由父进程和子进程共享)。
#include <unistd.h>
pid_t fork(void);  /* 返回值:子进程返回 0,父进程返回子进程 ID;若出错,返回 -1 */

    fork 函数被调用一次,但返回两次:子进程返回 0,而父进程返回新建子进程的进程 ID。子进程和父进程会继续执行 fork 调用之后的指令。子进程是父进程的副本,例如,子进程获得父进程的数据空间、堆和栈的副本,而不是共享这些存储空间部分,但子进程和父进程共享正文段。不过由于在 fork 之后经常跟随着 exec,所以现在很多实现并不执行一个父进程数据段、堆和栈的完全副本,而是使用了写时复制(Copy-On-Write,COW)技术。这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。如果父进程和子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一页。
    下面是一个 fork 函数使用示例,从中可以看到子进程对变量的修改并不影响父进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int globval = 6;	// external variable in initialized data.
char buf[] = "a write to stdout\n";

int main(void){
	int	var = 88;	// automatic variable on the stack
	pid_t pid;
    // 不写末尾的 null 字节
	if(write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1){
		printf("write error\n");
		exit(2);
	}
	printf("before fork\n");	// we don't flush stdout

	if((pid=fork()) < 0){
		printf("fork error\n");
		exit(2);
	}else if(pid == 0){		// child
		globval++;
		var++;
	}else{				// parent
		sleep(2);
	}

	printf("pid=%ld, glob=%d, var=%d\n", (long)getpid(), globval, var);
	exit(0);
}

    运行结果如下。
$ ./forkDemo.out
a write to stdout
before fork
pid=430, glob=7, var=89           # 子进程的变量值改变了
pid=429, blob=6, var=88
$
$ ./forkDemo.out > temp.out
$ cat temp.out
a write to stdout
before fork                       # 子进程输出一次
pid=432, blob=7, var=89
before fork                       # 父进程输出一次
pid=431, blob=6, var=88
$

    一般来说,fork 之后父进程和子进程的执行先后顺序是不确定的,这取决于内核所使用的调度算法。如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。
    本程序中需要注意 fork 与 I/O 函数之间的交互关系。由于 write 函数是不带缓冲的,write 又是在 fork 之前调用,所以其数据写到标准输出一次。但是标准 I/O 库是带缓冲的,如果标准输出连到终端设备,则它是行缓冲的;否则它是全缓冲的。所以当以交互方式运行该程序时,只得到该 printf 输出的行一次,因为标准输出缓冲区由换行符冲洗。而当将标准输出重定向到一个文件时,却得到 printf 输出行两次。这是因为在 fork 之前调用了 printf 一次,但当调用 fork 时,该行仍在缓冲区中,然后在将父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时父进程和子进程各自有了该行内容的缓冲区。在 exit 之前的第二个 printf 将其数据追加到已有的缓冲区中。当每个进程终止时,其缓冲区中的内容都被写到相应文件中。
    另外,还需要注意的是,fork 的一个特性是父进程的所有打开文件描述符都会被复制到子进程中。重要的一点是,父进程和子进程共享同一个文件偏移量(具体可参考文件共享一节)。所以如果上面程序中若没有调用 sleep() 之类的函数来等待子进程退出的话,它们的输出就可能是相互混合的(当然这里调用 sleep 其实也不一定能保证)。
    除了打开文件之外,父进程的其它大部分属性也由子进程继承,比如进程组 ID、实际组 ID、存储映像和资源限制等。
    父进程和子进程的区别主要如下:
    * fork 的返回值不同。
    * 进程 ID 不同。
    * 各自的父进程 ID 不同。
    * 子进程的 tms_utime、tms_stime、tms_cutime 和 tms_ustime 的值设置为 0。
    * 子进程不继承父进程设置的文件锁。
    * 子进程的未处理闹钟被清除。
    * 子进程的未处理信号集设置为空集。
    一般使 fork 失败的两个主要原因是:(a)系统中已经有了太多的进程,(b)该实际用户 ID 的进程总数超过了限制。
分享到:
评论

相关推荐

    创建新进程:fork函数:fork函数干什么? fork函数与vfork函数的区别在哪里?为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?

    它创建了一个完全相同的子进程副本,并返回一个进程标识符(PID)。fork 函数的返回值在父进程和子进程中不同:在父进程中,返回子进程的 PID;在子进程中,返回 0。 fork 函数的基本功能可以通过以下示例程序来...

    以下是一个简单的 C 语言程序,演示了如何使用进程标识符(PID):

    其次,进程标识符(Process Identifier,简称PID)是操作系统为每个进程分配的唯一标识号。它是一个整数类型的值,用于区分系统中的每一个进程。操作系统通过PID来管理进程的各种状态,如运行、挂起、停止等,并且...

    Windows 进程终止的消息标识符

    “Windows 进程终止的消息标识符”这一标题直接指出了本文档的主要内容:Windows操作系统中进程终止时所涉及的一些特定消息标识符。这些标识符对于理解进程终止的具体原因、调试问题以及进行系统维护具有重要意义。 ...

    操作系统(基本操作、轮换调度)完整代码

    /*进程标识符*/ int prio; /*进程优先数*/ int round; /*进程时间轮转时间片*/ int cputime; /*进程占用CPU时间*/ int needtime; /*进程到完成还要的时间*/ int count; /*计数器*/ char state; /*进程的状态*...

    Linux安全攻略——僵尸进程.pdf

    Linux安全攻略——僵尸进程.pdf中讨论了Linux操作系统中的进程管理机制,包括进程的概念、进程调度、进程树、进程标识符、进程生命周期等方面的知识点。 进程概念 在Linux操作系统中,进程是动态的,程序是静态的...

    易语言进程标示符取窗口句柄

    在IT领域,尤其是在系统编程和软件开发中,进程标识符(PID)和窗口句柄(HWND)是两个关键概念。本文将深入探讨易语言中如何使用这些概念,以及相关的函数和方法,如`EnumWindowsProc`、`GetWindowThreadProcessId`...

    vc获取进程名和PID的程序源码

    在Windows操作系统中,获取进程名和PID(进程标识符)是一项常见的系统编程任务,尤其对于开发者来说,了解如何实现这一功能至关重要。在这个场景下,我们关注的是使用VC++(Visual C++)和MFC(Microsoft ...

    如何杀excel进程的函数 kill excel进程的函数

    在探讨如何通过编程手段终止Excel进程的函数时,我们首先需要理解进程管理和Windows操作系统环境下的进程操作机制。在本文中,我们将深入解析如何利用C#语言中的`System.Diagnostics.Process`类来实现对Excel进程的...

    单处理器系统的进程调度+操作系统(c语言版)

    进程控制块(PCB)是进程调度的核心结构,每个进程对应一个PCB,用于存储进程的相关信息,如进程标识符、进程状态、寄存器内容等。在单处理器系统中,PCB通常包括以下组成部分: * 进程标识符:用于唯一标识每个...

    操作系统课程设计—进程控制

    首先可利用GetCurrentProcessId() 函数来查看的进程特性是进程标识符 (PID),返回的PID在整个系统中都可使用。其他的可显示当前进程信息的API函数如GetStartupInfo()和GetProcessShutdownParameters() 可给出进程的...

    时间片轮法进程调度操作系统实验报告.doc

    3. 用户输入进程标识符以及进程所需的时间,申请空间存放进程 PCB 信息 4. 每一个时间片完毕输出各进程的进程号,CPU 时间(即已经占用的 CPU 时间),所需时间(即还需要的 CPU 时间),以及状态(即用 W 表示等待...

    时间片轮法完成进程调度(操作系统实验报告).doc

    3. 用户输入进程标识符和所需时间:用户将输入进程标识符和所需时间,我们将根据用户的输入来初始化进程的相关信息。 4. 每一个时间片结束输出各进程的信息:在每个时间片结束时,我们将输出各进程的进程号、已运行...

    1操作系统实验五.docx

    实验目的是通过实际操作加深对进程同步互斥概念的理解,掌握信号量的使用方法和P、V操作函数的定义。 1. **信号量与P、V操作**: - 信号量是一种同步工具,用于解决多个进程对共享资源的访问冲突。 - P操作(即...

    操作系统 进程创建实验报告

    1. **进程表项**:包括进程标识符(PID)、用户标识符(UID)、进程状态、事件描述符等。 2. **U区**:用于存放进程表项的扩充信息,如进程表项指针、用户标识符等。 3. **系统区表项**:用于记录各个段在物理存储器...

    操作系统实验一:进程控制

    它创建一个新的进程,并返回进程标识符(PID)。fork()调用返回两次,一次是在父进程中,返回子进程的 PID;另一次是在子进程中,返回 0。fork()调用可以用来创建多个子进程,以实现并发执行。 三、wait()系统调用 ...

    fork 函数详解

    每个进程都有一个独特的进程标识符(process ID),可以通过 getpid 函数获得,还有一个记录父进程 pid 的变量,可以通过 getppid 函数获得变量的值。 fork 函数执行完毕后,出现两个进程,它们的内容基本完全一样...

    操作系统Linux实验报告三:进程管理及进程通信.doc

    答:子进程被创建后,核心将其分配一个进程表项和进程标识符,检查同时运行的进程数目,并且拷贝进程表项的数据,由子进程继承父进程所有文件。 * 管道通信的实现方法是什么?答:管道通信是指使用pipe()系统调用...

    进程创建和杀死

    它包含了进程的所有信息,如进程标识符、进程状态、优先级、内存地址等。PCB 是进程的唯一标识符,它是操作系统管理进程的关键。 3. 创建新的进程: 在实验中,我们使用 C 语言编写的 create 函数来创建新的进程。...

    进程操作 关闭进程 进程ID

    进程ID是操作系统分配给每个进程的唯一标识符,可以通过`OpenProcess`函数获得进程句柄,再通过`GetProcessId`函数获取对应的进程ID。`OpenProcess`需要进程ID和访问权限作为参数,访问权限可以包括读取、写入、挂起...

    linux的进程函数

    在`fork()`成功执行后,调用进程(父进程)和新创建的进程(子进程)都会继续执行`fork()`之后的代码,但它们拥有各自的进程标识符(PID)、内存空间、文件描述符等资源。 1. 进程复制:`fork()`会复制父进程的所有...

Global site tag (gtag.js) - Google Analytics