#include <unistd.h>
pid_t getpid(void);
Returns: process ID of calling process
pid_t getppid(void);
Returns: parent process ID of calling process
uid_t getuid(void);
Returns: real user ID of calling process
uid_t geteuid(void);
Returns: effective user ID of calling process
gid_t getgid(void);
Returns: real group ID of calling process
gid_t getegid(void);
Returns: effective group ID of calling process
- 父进程等待子进程完成。这种情况下,父进程无需对其描述符做任何处理。
- 父进程和子进程各自执行不同的程序段。这种情况下,fork之后,父子进程各自它们不需要使用的文件描述符。
strlen和sizeof的区别:前者不包括null字节,一次函数调用;后者包括null字节,编译时计算
除了文件描述符之外,父进程的很多其他属性也由子进程继承,包括:
- 实际用户ID、实际组ID、有效用户ID、有效组ID
- 附属组ID
- 进程组ID
- 会话ID
- 控制终端
- SUID和SGID标志(stat结构的st_mode成员)
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字umask
- 信号屏蔽和处理
- 对任一打开文件描述符的执行时关闭(close-on-exec)标志
- 环境
- 连接的共享存储段
- 存储映像
- 资源限制
- 是否继承nice值由具体实现自行决定
父进程和子进程之间的区别具体如下:
- fork的返回值不同
- pid不同
- 这两个进程的父进程不同
- 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0
- 子进程不继承父进程设置的文件锁
- 子进程的未处理闹钟被清除
- 子进程的未处理信号集设置为空集
fork失败的两个主要原因:
- 系统中已经有了太多的进程
- 该实际用户ID的进程总数超过了系统限制
fork有以下两种用法:
- 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务器中是常见的。
- 一个进程要执行一个不同的程序。这对shell是常见的情况。某些系统将fork+exec组合成一个操作spawn
- vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序,故不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不管在子进程调用exec或exit之前,它在父进程的空间中运行。
- 另一个区别是vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。故如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
正常终止
方式:
- 从main中执行return,等效于调用exit
- 调用exit函数,调用各终止处理程序,关闭标准I/O流,最后调用_exit函数
- 调用_exit或_Exit
- 进程的最后一个线程在其启动例程执行return语句,该进程以终止状态0返回
- 进程的最后一个线程调用pthread_exit,进程终止状态总是0
异常终止
方式:
- 调用abort,它产生SIGABRT信号
- 当进程接收到某些信号时,信号可由进程自身(如调用abort函数)、其他进程或内核产生
- 最后一个线程对“取消”请求做出响应
注意:“退出状态”(3个exit函数的参数或main的返回值)区别于“终止状态”。在最后调用_exit时,内核将退出状态转换为终止状态。
如果父进程在子进程之前终止,则称子进程为孤儿进程
。子进程 ppid变为1,称这些进程由init进程收养
。一个init进程收养的进程终止时,init会调用一个wait函数取得其终止状态,防止它成为僵尸进程。
僵尸进程
zombie/defunct。#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
Both return: process ID if OK, 0 (see later), or −1 on error
- 如果其所有子进程都还在运行,则阻塞
- 如果一个子进程终止,正等待其父进程获取其终止状态,则取得该子进程的终止状态立即返回
- 如果它没有任何子进程,则立即出错返回
- waitpid有一选项,可使调用者不阻塞
- waitpid可以控制它所等待的进程
若statloc不是NULL,则终止进程的终止状态就存放在它所指向的单元内。该整型状态字由实现定义,其中某些位表示退出状态(正常返回),其他位则指示信号编号(异常返回),有一位指示是否产生了core文件。
waitpid函数中的pid参数的解释:
pid == -1,等待任一子进程,等价于wait函数
pid > 0,等待pid等于该值的子进程
pid == 0,等待组ID等于调用进程组ID的任一子进程
pid < 0,等待组ID等于pid绝对值的任一子进程
waitpid函数中的options参数:WNOHANG(不阻塞)、WCONTINUED、WUNTRACED
如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,实现这一要求的诀窍是调用fork两次。
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* first child */
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/*
* We’re the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here’s where we’d continue executing, knowing that when
* we’re done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %ld\n", (long)getppid());
exit(0);
}
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");
/*
* We’re the parent (the original process); we continue executing,
* knowing that we’re not the parent of the second child.
*/
exit(0);
}
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Returns: 0 if OK, −1 on error
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
Both return: process ID if OK, 0, or −1 on error
man 2 getrusage
#include "apue.h"
TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
/* child does whatever is necessary ... */
TELL_PARENT(getppid()); /* tell parent we’re done */
WAIT_PARENT(); /* and wait for parent */
/* and the child continues on its way ... */
exit(0);
}
/* parent does whatever is necessary ... */
TELL_CHILD(pid); /* tell child we’re done */
WAIT_CHILD(); /* and wait for child */
/* and the parent continues on its way ... */
exit(0);
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
All seven return: −1 on error, no return on success
p代表使用调用进程中的environ变量为新程序复制现有的环境
在执行exec后,pid没有改变。但新程序从调用进程继承了下列属性:
- pid和ppid
- 实际用户ID和实际组ID
- 附属组ID
- 进程组ID
- 会话ID
- 控制终端
- 闹钟尚余留的时间
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字umask
- 文件锁
- 进程信号屏蔽
- 未处理信号
- 资源限制
- nice值
- tms_utime、tms_stime、tms_cutime、tms_cstime
- 对打开文件的处理:若文件描述符的执行时关闭(close-on-exec,默认通过fcntl设置)标志被设置(默认没有设置),则在执行exec时关闭该描述符;否则仍保持打开。POSIX.1明确要求在exec时关闭打开目录流。
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
Both return: 0 if OK, −1 on error
ruid
、有效用户ID euid
、保存的设置用户ID sSUID
。假定_POSIX_SAVED_IDS为真
- 若进程具有root特权,则setuid函数将ruid、euid和sSUID设置为参数uid的值
- 若进程没有root特权,但uid等于ruid或sSUID,则setuid函数只将euid设置为uid
- 如果上面两个条件都不满足,则errno设置为EPERM,并返回-1
- 只有root进程可以更改ruid。通常,ruid是在用户登录时,由login程序设置的,而且决不会改变它。
- 仅当对程序文件设置了SUID位,exec函数才设置euid。没有设置SUID位,则euid = ruid。
- sSUID是由exec复制euid而得到的。
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
Both return: 0 if OK, −1 on error
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
Both return: 0 if OK, −1 on error
对于root用户,可将其euid设置为uid,而ruid、sSUID保持不变
组ID:上面所说的一切都以类似方式适用于各个组ID。附属组ID不受setgid、setregid、setegid函数的影响。
#include <stdlib.h>
int system(const char *cmdstring);
Returns: (see below)
typedef u_short comp_t; /* 3-bit base 8 exponent; 13-bit fraction */
struct acct
{
char ac_flag; /* flag (see Figure 8.26) */
char ac_stat; /* termination status (signal & core flag only) */
/* (Solaris only) */
uid_t ac_uid; /* real user ID */
gid_t ac_gid; /* real group ID */
dev_t ac_tty; /* controlling terminal */
time_t ac_btime; /* starting calendar time */
comp_t ac_utime; /* user CPU time */
comp_t ac_stime; /* system CPU time */
comp_t ac_etime; /* elapsed time */
comp_t ac_mem; /* average memory usage */
comp_t ac_io; /* bytes transferred (by read and write) */
/* "blocks" on BSD systems */
comp_t ac_rw; /* blocks read or written */
/* (not present on BSD systems) */
char ac_comm[8]; /* command name: [8] for Solaris, */
/* [10] for Mac OS X, [16] for FreeBSD, and */
/* [17] for Linux */
};
#include <unistd.h>
char *getlogin(void);
Returns: pointer to string giving login name if OK, NULL on error
#include <unistd.h>
int nice(int incr);
Returns: new nice value − NZERO if OK, −1 on error
#include <sys/resource.h>
int getpriority(int which, id_t who);
Returns: nice value between −NZERO and NZERO−1 if OK, −1 on error
#include <sys/resource.h>
int setpriority(int which, id_t who, int value);
Returns: 0 if OK, −1 on error
#include <sys/times.h>
clock_t times(struct tms *buf );
Returns: elapsed wall clock time in clock ticks if OK, −1 on error
struct tms {
clock_t tms_utime; /* user CPU time */
clock_t tms_stime; /* system CPU time */
clock_t tms_cutime; /* user CPU time, terminated children */
clock_t tms_cstime; /* system CPU time, terminated children */
};