每个进程都有一个非负整型表示的唯一进程ID。进程ID是可重用的。
ID为0的进程通常是调度进程,常常被称为交换进程(swapper)。 该进程是内核的一部分,它并不执行任何磁盘上的程序。
ID为1通常是init进程,在自举过程结束时由内核调用。
#include
#include
#include
int main(int argc, char* argv[]) {
printf("%d\n", getpid());
printf("%d\n", getppid());
printf("%d\n", getuid());
printf("%d\n", geteuid());
printf("%d\n", getgid());
printf("%d\n", getegid());
exit(0);
}
一个现有的进程可以调用fork函数创建一个新进程。子进程获得父进程数据空间、堆和栈的副本。 父进程和子进程共享正文段。
作为优化,某些实现中不完全创建副本,而使用写时复制技术。父进程和子进程共享这些区域,但是如果任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本。
例子
#include "apue.h"
int globalvar = 6;
char buf[] = "hello, huahua\n";
int main(void) {
int var;
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf) -1) != sizeof(buf) - 1) //sizeof包含了最后的null
err_sys("write error");
printf("before fork\n");
//写入缓冲区,如果是在shell运行是行缓冲,被换行符刷新;
// 如果是写入到文件是全缓冲,不会刷新,缓冲内容会带到子进程
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) { //子进程
globalvar++; //数据空间独立,只有子进程的变量会被修改
var++;
} else { //父进程
sleep(2);
}
printf("pid: %d, global var: %d, var: %d\n", pid, globalvar, var);
exit(0);
}
父进程和子进程每个相同的打开描述符共享一个文件表项。父进程和子进程共享同一个文件偏移量。
在fork之后处理文件描述符的方式:
使fork失败的两个主要原因:
fork有以下两种用法:
vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit。
在子进程调用exec或exit之前,它在父进程的空间中运行。
vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。
#include "apue.h"
int globalvar = 6;
int main(void) {
int var;
pid_t pid;
var = 88;
printf("before fork\n");
if ((pid = vfork()) < 0)
err_sys("fork error");
else if (pid == 0) { //子进程
globalvar++; //会修改父进程的数据
var++;
_exit(0); //退出时不会刷新缓冲区,如果同时关闭了fd,则不会输出任何内容
}
printf("pid: %d, global var: %d, var: %d\n", pid, globalvar, var);
exit(0);
}
进程终止的情况
(1) 从main返回;
(2) 调用exit, 执行各种终止处理程序
(3) 调用_exit或_Exit,不执行终止处理程序或信号处理程序
(4) 最后一个线程从其启动例程返回。线程的返回值不是进程的终止值。
(5) 从最后一个线程调用pthread_exit
(6) 调用abort,它产生SIGABRT信号。
(7) 接到一个信号,信号可由进程自身(如调用abort函数)、其他进程或内核产生
(8) 最后一个线程对取消请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
孤儿进程
对于父进程已经终止的所有进程,它们的父进程都改变为 init 进程。只要有一个子进程终止,init 就会调用一个wait函数取得其终止状态。所以init的子进程不会成为僵尸进程。
僵尸进程
一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵尸进程(zombie)。
调用wait或waitpid的进程时:
#include "apue.h"
#include "sys/wait.h"
void pr_exit(int status) {
if (WIFEXITED(status))
printf("normal exit, exit status= %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number= %d%s\n", WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status)? "core file exits" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}
int main(void) {
int status;
pid_t pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
exit(7); //正常退出
if (wait(&status) != pid)
err_sys("wait error");
pr_exit(status);
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
abort(); //异常退出
if (wait(&status) != pid)
err_sys("wait error");
pr_exit(status);
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
status /= 0; //异常退出
if (wait(&status) != pid)
err_sys("wait error");
pr_exit(status);
exit(0);
}
waitpid通过指定pid设置不同选项。
pid > 0, 等待指定pid
pid == -1 等待任一子进程
pid == 0 等待组ID等于调用进程组ID的任一子进程
pid < -1 等待组ID等于pid绝对值的任一子进程
fork之后执行的操作如果对子进程和父进程的执行顺序有要求,则会产生竞争条件。为了处理竞争条件,需要子进程和父进程之间通过信号传递。
例子1,进程竞争展示,输出的字符顺序可能会混乱。
#include "apue.h"
static void charatatime(char *);
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
charatatime("output from child,output from child,output from child,output from child,output from child,output from child,output from child\n");
} else {
charatatime("output from parent,output from parent,output from parent,output from parent,output from parent,output from parent,output from parent\n");
}
exit(0);
}
static void charatatime(char *str)
{
char *ptr;
int c;
setbuf(stdout, NULL); /* set unbuffered */
for (ptr = str; (c = *ptr++) != 0; )
putc(c, stdout);
}
添加同步控制
#include "apue.h"
static void charatatime(char *);
int main(void)
{
pid_t pid;
TELL_WAIT();
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) {
WAIT_PARENT(); /* parent goes first */
charatatime("output from child\n");
} else {
charatatime("output from parent\n");
TELL_CHILD(pid);
}
exit(0);
}
static void charatatime(char *str)
{
char *ptr;
int c;
setbuf(stdout, NULL); /* set unbuffered */
for (ptr = str; (c = *ptr++) != 0; )
putc(c, stdout);
}
这里用到的信号处理函数包含在time_wait.c中,在CMakeList中修改
#添加到可执行文件中
add_executable(${name} ${file} ../include/error.c
../lib/tellwait.c)
当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。
exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
#include
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[]);
有7种不同的exec函数,这7个函数中只有execve是内核的系统调用。不同之处在于:
他们相互之间存在调用关系
execlp->execvp->execv->execve
execl->execv->execve
execle->execve
fexecve->execve
例子
#include "apue.h"
#include
char* env_init[] = {"PATH=/tmp", "USER=UNKNOWN", NULL};
int main(void) {
pid_t pid;
if ((pid = vfork()) < 0)
err_sys("fork error");
else if (pid == 0) { //子进程
if (execle("./echoall", "echoall", "hello", "huahua", (char*)0, env_init) < 0)
err_sys("execle error");
}
if (waitpid(pid, NULL, 0) < 0)
err_sys("wait error");
if ((pid = vfork()) < 0)
err_sys("fork error");
else if (pid == 0) {
if (execlp("echoall", "achoall", "hello", "huahua", (char *)0) < 0)
err_sys("execlp error");
}
exit(0);
}
就是解释器脚本文件,初始行的格式通常是
#! pathname [ optional-argument ]
pathname指定解释器路径。
内核使调用exec函数的进程实际执行的并不是该解释器文件,而是在该解释器文件第一行中pathname所指定的文件。
解释器文件的好处:
system函数执行传入的命令,在其实现中调用了fork、exec和waitpid。使用system的优点是system进行了所需的各种出错处理以及各种信号处理。
#include
int system(const char *cmdstring);
例子
#include
#include
#include
#include "apue.h"
int system(const char* cmd) {
pid_t pid;
int status;
if (cmd == NULL)
return(1);
if ((pid = fork()) < 0)
status = -1;
else if (pid == 0) { //子进程执行传入命令
execl("/bin/sh", "sh", "-c", cmd, (char*)0);
_exit(127);
} else {
while (waitpid(pid, &status, 0) < 0) { //父进程等待子进程退出
if (errno != EINTR) {
status = -1;
break;
}
}
}
return status;
}
void pr_exit(int status) {
if (WIFEXITED(status))
printf("normal exit, exit status= %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number= %d%s\n", WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status)? "core file exits" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}
int main(void) {
int status;
if ((status = system("date")) < 0)
err_sys("system error");
pr_exit(status);
if ((status = system("nosuchcommand")) < 0)
err_sys("system error");
pr_exit(status);
if ((status = system("who; exit 44")) < 0)
err_sys("system error");
pr_exit(status);
exit(0);
}
注意,设置用户ID或设置组ID程序决不应调用system函数,有安全问题。如果一个进程正以特殊的权限(设置用户ID或设置组ID) 运行,它又想生成另一个进程执行另一个程序, 则它应当直接使用fork和exec, 而且在fork之后、 exec之前要更改回普通权限。
获取用户登录名。
#include
char *getlogin(void);
例子
#include "unistd.h"
#include "apue.h"
int main(void) {
char* user;
if ((user = getlogin()) != NULL)
printf("%s", user);
exit(0);
}
进程的调度策略和调度优先级是由内核确定的。 可以通过nice获得和调整进程的优先级。
进程可以降低优先级,只有特权进程可以提高优先级。
例子
#include "unistd.h"
#include "apue.h"
#include
int main(void) {
int pri;
pri = nice(0);
printf("nice: %d, errno: %d\n", pri, errno); //0, 0
pri = nice(100);
printf("nice: %d, errno: %d\n", pri, errno); //19, 0
pri = nice(-100);
printf("nice: %d, errno: %d\n", pri, errno); //-1, 1
exit(0);
}
其他函数
#include
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int value);
获取进程的系统CPU时间,用户CPU时间和时钟时间。
#include
clock_t times(struct tms *buf));
例子
static void
do_cmd(char *cmd) /* execute and time the "cmd" */
{
struct tms tmsstart, tmsend;
clock_t start, end;
int status;
printf("\ncommand: %s\n", cmd);
if ((start = times(&tmsstart)) == -1) /* starting values */
err_sys("times error");
if ((status = system(cmd)) < 0) /* execute command */
err_sys("system() error");
if ((end = times(&tmsend)) == -1) /* ending values */
err_sys("times error");
pr_times(end-start, &tmsstart, &tmsend);
pr_exit(status);
}