我是靠谱客的博主 单身白羊,最近开发中收集的这篇文章主要介绍进程标识符操作函数,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

每个进程都有一个非负整型表示的唯一进程 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 stdoutn";

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 errorn");
exit(2);
}
printf("before forkn"); // we don't flush stdout

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

printf("pid=%ld, glob=%d, var=%dn", (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 的一个特性是父进程的所有打开文件描述符都会被复制到子进程中。重要的一点是,父进程和子进程共享同一个文件偏移量(具体可参考[url=http://aisxyz.iteye.com/admin/blogs/2377661]文件共享[/url]一节)。所以如果上面程序中若没有调用 sleep() 之类的函数来等待子进程退出的话,它们的输出就可能是相互混合的(当然这里调用 sleep 其实也不一定能保证)。
除了打开文件之外,父进程的其它大部分属性也由子进程继承,比如进程组 ID、实际组 ID、存储映像和资源限制等。
父进程和子进程的区别主要如下:
* fork 的返回值不同。
* 进程 ID 不同。
* 各自的父进程 ID 不同。
* 子进程的 tms_utime、tms_stime、tms_cutime 和 tms_ustime 的值设置为 0。
* 子进程不继承父进程设置的文件锁。
* 子进程的未处理闹钟被清除。
* 子进程的未处理信号集设置为空集。
一般使 fork 失败的两个主要原因是:(a)系统中已经有了太多的进程,(b)该实际用户 ID 的进程总数超过了限制。

最后

以上就是单身白羊为你收集整理的进程标识符操作函数的全部内容,希望文章能够帮你解决进程标识符操作函数所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部