#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
#include <unistd.h>
pid_t fork(void);
//返回值:子进程返回0;父进程返回进程ID;若出错,返回-1
将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。
fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总可以调用getppid以获得其父进程的进程ID。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所有用的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全副本。作为代替,使用了写时复制(Copy-On-Write, cow)技术。这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globvar = 6;
char buf[] = "a write to stdout\n";
int main()
{
int var;
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
{
printf("write error!\n");
exit(1);
}
printf("Befor fork\n");
if ((pid = fork()) < 0)
{
printf("Fork error\n");
exit(1);
}
else if (pid == 0)
{
//子进程
globvar++;
var ++;
}
else
sleep(2);
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
exit(0);
}
yanke@vm:~/Code/apue/ch8ForkContral$ ./a.out
a write to stdout
Befor fork
pid = 3473, glob = 7, var = 89
pid = 3472, glob = 6, var = 88
yanke@vm:~/Code/apue/ch8ForkContral$ ./a.out > tmp.out
yanke@vm:~/Code/apue/ch8ForkContral$ cat tmp.out
a write to stdout
Befor fork
pid = 3478, glob = 7, var = 89
Befor fork
pid = 3477, glob = 6, var = 88
在fork之后处理文件描述符有以下两种常见的情况。
子进程继承的属性:
父进程和子进程之间的区别:
fork失败的两个主要的原因
fork有以下两种用法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globvar = 6;
int main()
{
int var;
pid_t pid;
var = 88;
printf("Befor fork\n");
if ((pid = vfork()) < 0)
{
printf("Fork error\n");
exit(1);
}
else if (pid == 0)
{
//子进程
globvar++;
var ++;
_exit(0);
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out
Befor fork
pid = 5822, glob = 7, var = 89
可见,子进程改变了父进程中的值,因为子进程在父进程的空间中执行
进程有5种正常终止:
进程3种异常终止:
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程。ps(1)命令将僵死进程的状态打印为Z。如果编写一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,不然这些子进程终止后就会变成僵死进程。
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号(内核保存了子进程的终止信息)。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)
调用wait和waitpid的进程可能会发生:
#include <sys/wait.h>
pid_t wait( int *statloc );
返回值:若成功则返回已终止子进程ID,若出错则返回-1
pid_t waitpid( pid_t pid, int *statloc, int options );
返回值:若成功则返回状态改变的子进程ID,若出错则返回-1,若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0
例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
void ErrorExit(const char* m)
{
perror(m);
exit(EXIT_FAILURE);
}
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination,exit status = %d\n",\
WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("abnoraml termination, signal = number = %d.\n",\
WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",\
WSTOPSIG(status));
}
int main()
{
pid_t pid;
int status;
if((pid = fork()) < 0)
ErrorExit("fork error!");
else if(pid == 0)
exit(7);
if (wait(&status) != pid)
ErrorExit("wait error!");
pr_exit(status);
if ((pid = fork()) < 0)
ErrorExit("fork error!");
else if (pid == 0)
abort();
if (wait(&status) != pid)
ErrorExit("wait error!");
pr_exit(status);
if ((pid = fork())<0)
ErrorExit("wait error!");
else if (pid == 0)
status /= 0;
if (wait(&status) != pid)
ErrorExit("wait error!");
pr_exit(status);
exit(0);
}
normal termination,exit status = 7
abnoraml termination, signal = number = 6.
abnoraml termination, signal = number = 8.
waitpid函数中pid的作用是:
waitpid函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由status指向的存储单元中。对于wait,其唯一的出错是调用进程没有子进程(函数调用被一个信号中断时,也可能返回另一种出错)。但是对于waitpid,如果指定的进程或进程组不存在,或者参数pid指定的进程不是调用进程的子进程则都将出错。
options参数使我们能进一步控制waitpid的操作。此参数可以是0:
Single UNIX Specification的XSI扩展包括了另一个取进程终止状态的函数——waitid,此函数类似于waitpid,但提供了更多的灵活性。
#include <sys/wait.h>
int waitid( idtype_t idtype, id_t id, siginfo_t *infop, int options );
返回值:若成功则返回0,若出错则返回-1
#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 );
两个函数返回值:若成功则返回进程ID,若出错则返回-1
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞争条件。如果一个进程希望等待一个子进程终止,则它必须调用一种wait函数。如果一个进程要等待其父进程终止,则可使用下列形式的循环:
while ( getppid() != 1 )
sleep( 1 );
这种形式的循环(称为轮询(polling))的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后再进行条件测试。为了避免竞争条件和轮询,在UNIX中可以使用信号机制。也可使用各种形式的进程间通信(IPC)。
用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用一个全新的程序替换了当前进程的正文、数据、堆和栈段。
用fork可以创建新进程,用exec可以执行新程序。exit函数和两个wait函数处理终止和等待终止。这些是我们需要的基本的进程控制原语。
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[] );
//6个函数返回值:若出错则返回-1,若成功则不返回值
PATH=/bin:/usr/bin:/usr/local/bin/:.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int i;
char** ptr;
extern char** environ;
for (i = 0; i < argc; ++i)
printf("argv[%d]:%s\n", i, argv[i]);
for (ptr = environ; *ptr != 0; ++ptr)
{
printf("%s\n", *ptr);
}
exit(0);
}
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
char* env_inti[] = {"USER=unknow", "PATH=/tmp", NULL};
int main()
{
pid_t pid;
if ((pid = fork()) < 0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
if (execle("/home/ubuntu/Code/apue/ch8ForkContral/echoall", "echoall", "yan", "ke", "scut", (char*)0, env_inti) < 0)
{
printf("execle error\n");
exit(1);
}
}
if (waitpid(pid, NULL, 0) < 0)
{
printf("wait error!\n");
exit(1);
}
else if (pid == 0)
{
if (execlp("echoall", "echoall", "only one arg", (char*)0) < 0)
{
printf("execlp error\n");
exit(1);
}
}
exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out
argv[0]:echoall
argv[1]:yan
argv[2]:ke
argv[3]:scut
USER=unknow
PATH=/tmp
最后总结一下使用exec函数时需要考虑哪些参数:首先,要执行的新程序名(带不带路径);其次,命令行参数(列表形式还是指针数组形式);最后,环境表(要不要传递环境表).
在UNIX系统中,特权(例如能改变当前日期的表示法以及访问控制(例如,能否读、写一特定文件))是基于用户ID和组ID的。当程序需要增加特权,或需要访问当前并不允许访问的资源时,我们需要更换自己的用户ID或组ID,使得新ID具有合适的特权或访问权限。与此类似,当程序需要降低其特权或阻止对某些资源的访问时,也需要更换用户ID或组ID,从而使新ID不具有相应特权或访问这些资源的能力。在设计应用程序时,我们总是试图使用最小特权(least privilege)模型。
有时,在从一个服务器到另一个服务器迁移服务器或应用程序时将需要更改 UID 或 GID。还有,在管理员操作失误时也需要更改 UID 或 GID。在使用 AIX High-Availability Cluster Multiprocessing (HACMP) 进行集群管理的环境中,所有集群服务器必须始终具有相同的 UID 和 GID;否则故障转移流程就无法正确工作。
可以用setuid函数设置实际用户ID和有效用户ID。与此类似,可以用setgid函数设置实际组ID和有效组ID。
#include <unistd.h>
int setuid( uid_t uid );
int setgid( gid_t gid );
两个函数返回值:若成功则返回0,若出错则返回-1
规则:
注意:
setreuid和setregid函数:交换实际用户ID(组ID)和有效用户ID(组ID)的值
#include <unistd.h>
int setreuid( uid_t ruid, uid_t euid );
int setregid( gid_t rgid, gid_t egid );
两个函数返回值:若成功则返回0,若出错则返回-1
seteuid和setegid函数:类似于setuid和setgid,但只更改有效用户ID和有效组ID。
#include <unistd.h>
int seteuid( uid_t uid );
int setegid( gid_t gid );
两个函数返回值:若成功则返回0,若出错则返回-1
关于用户ID所说明的一切都适用于组ID。附加组ID不受setgid、setregid或setegid函数的影响。
所有现今的UNIX系统都支持解释器文件(interpreter file)(也可称为解释器脚本)。这种文件是文本文件,其起始行格式是:#! pathname [optional-argument]
.感叹号和pathname之间的空格是可选的。最常见的解释器文件以下列行开始:
#!/bin/sh
#include<stdlib.h>
int system(const char * string);
返回值:
=-1:出现错误
0:成功退出的子进程的id
在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题
#include <stdlib.h>
#include <stdio.h>
int main()
{
system("ls -al ./");
exit(0);
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch8ForkContral$ ./a.out
total 88
drwxrwxr-x 2 ubuntu ubuntu 4096 Nov 2 09:54 .
drwxrwxr-x 16 ubuntu ubuntu 4096 Oct 29 15:08 ..
-rwxrwxr-x 1 ubuntu ubuntu 8632 Nov 2 09:54 a.out
-rwxrwxr-x 1 ubuntu ubuntu 113 Sep 16 11:26 awktest
-rwxrwxr-x 1 ubuntu ubuntu 8715 Sep 15 10:59 echoall
大多数UNIX系统提供了一个选项以进行进程会计(process accounting)处理。启用该选项后,每当进程结束时内核就写一个会计记录。典型的会计记录包含总量较小的二进制数据,一般包括命令名、所使用的CPU时间总量、用户ID和组ID、启动时间等
函数(acct)用于启用和禁用进程会计.
进程会计的结构体如下
typedef u_short comp_t; /* 3-bit base 8 exponent; 13-bit fraction */
struct acct
{
char ac_flag; /* flag */
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 (clock ticks) */
comp_t ac_stime; /* system CPU time (clock ticks) */
comp_t ac_etime; /* elapsed time (clock ticks) */
comp_t ac_mem; /* average memory usage */
comp_t ac_io; /* bytes transferred (by read and write) */
/* "blocks" on BSD system */
comp_t ac_rw; /* blocks read or written */
/* (not present on BSD system) */
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);
返回值:若成功则返回指向登录名字符串的指针,若出错则返回NULL
nice值越小,优先级越高
#include<unistd.h>
int nice(int incr); //返回值:若成功,返回新的nice值;若出错,返回-1
和进程nice值相关的函数:
(1)getpriority函数可以像nice函数获取进程nice值,而且他还可以获取一组相关进程的nice值.
#include <sys/resource.h>
int getpriority(int which, id_t who); //返回值:若成功,返回nice值;若失败,返回-1
(2)setpriority
#include <sys/resource.h>
int setpriority(int which,id_t who, int value);//返回值:若成功,返回0;若失败,返回-1
使用下面函数可以获得进程执行时间:
#include <unistd.h>
struct tms {
clock_t tms_utime; /* user time */
clock_t tms_stime; /* system time */
clock_t tms_cutime; /* user time of children */
clock_t tms_cstime; /* system time of children */
};
clock_t times(struct tms* buf); //返回wall clock time.但是需要通过差值来反映
[1] https://www.ibm.com/developerworks/cn/aix/library/au-satuidgid/
[2] http://blog.csdn.net/mmshixing/article/details/51305138