8– 进程控制
本章介绍了UNIX系统的进程控制 : 创建新进程 ,执行程序和进程终止 ,还说明了进程属性的各种ID .
8.2进程标识 :
每个进程都有一个非负整形标识唯一进程ID .
常用其作为其他标识符的一部分以保证其唯一性 .
例如 :应用程序有时就把进程ID作为名字的一部分来创建一个唯一的文件 .
一些专用进程 :ID为0的进程通常是调度进程 .
ID为1的通常是init进程 ,此进程负责在自举内核后启动一个UNIX系统 ,init通常读取与系统有关的初始化文件 .Init进程绝不会终止 .
8.3fork :
由fork创建的新进程被称为子进程 :被调用一次返回两次 :子进程 :0 父进程 :子进程ID;
一个进程只会有一个父进程 ,所以子进程总是可以调用getppid以获得其父进程的进程ID .
Fork之后,子进程是父进程的副本,例如 :子进程获得父进程的数据空间 ,堆和栈的副本 .这是子进程所拥有的副本 ,父子进程并不共享这些存储空间部分 . 父进程和子进程共享正文段 .
8.4vfork
Vfork函数用于创建一个新进程 ,而该新进程的目的是exec一个新程序 .它并不将父进程的地址空间完全复制到子进程中 ,因为子进程会立即调用exec .
Vfork和fork的一个区别是 :vfork保证子进程先运行(这段时间内核会使父进程休眠) ,它在调用exec或exit之后父进程才可能被调度运行 .
8.6 wait 和 waitpid
一个进程正常或异常终止时 ,内核就向其父进程发送SIGCHILD信号 .因为子进程终止是个异步事件 ,所以这种信号也是内核向父进程发的异步通知 .
调用wait和waitpid会发生什么 ;
1. 如果所有子进程都还在运行 ,则阻塞 .
2. 若一个子进程已经终止 ,正等待父进程获取其终止状态 ,则取得该子进程的终止状态立即返回 .
8.9竞争条件 :
当多个进程都企图对共享数据进行某种处理 ,而最后结果又取决于进程运行顺序时 我们认为发生了竞争条件 .
8.10exec
8.11更改用户ID和组ID .
本章介绍了UNIX系统的进程控制 : 创建新进程 ,执行程序和进程终止 ,还说明了进程属性的各种ID .
8.2进程标识 :
每个进程都有一个非负整形标识唯一进程ID .
常用其作为其他标识符的一部分以保证其唯一性 .
例如 :应用程序有时就把进程ID作为名字的一部分来创建一个唯一的文件 .
一些专用进程 :ID为0的进程通常是调度进程 .
ID为1的通常是init进程 ,此进程负责在自举内核后启动一个UNIX系统 ,init通常读取与系统有关的初始化文件 .Init进程绝不会终止 .
8.3fork :
由fork创建的新进程被称为子进程 :被调用一次返回两次 :子进程 :0 父进程 :子进程ID;
一个进程只会有一个父进程 ,所以子进程总是可以调用getppid以获得其父进程的进程ID .
Fork之后,子进程是父进程的副本,例如 :子进程获得父进程的数据空间 ,堆和栈的副本 .这是子进程所拥有的副本 ,父子进程并不共享这些存储空间部分 . 父进程和子进程共享正文段 .
例程 :
/*****************************************************
*fork函数 pid_t fork(void) 用一个现有进程创建新进程
*新进程 被称为 子进程
*子进程是父 进程 的副本 子进程 会获得 父进程的 程序数
*据空间 堆和栈的 副本 但 父进程和 子进程 共享正文段
*
*
*******************************************************/
#include<apue.h>
#include<myerr.h>
int glob_var =66;
char buf [] = "a write to stdout\n";
int main()
{
int var = 6;
pid_t pid;
if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1) err_sys("write error");
/*sizeof(宏定义) 计算包括NULL在内的字符串长度
*父进程和子进程 共享一个 文件偏移量
*此处 父进程等待子进程时 子进程 写到标准输出
*而在 子进程终止后 父进程也写到标准输出
*/
printf("before fork\n");
/*我们不冲洗缓冲区
*如果标准输出连接到终端设备 则它是行缓冲 出现一次(父进程调用后马上冲了)
*如果重定向到文件 输出 两次 因为当调用fork 时
*该 缓冲数据人 也被复制进 子进程 exit 之前 第二个 pri
*ntf 将数据追加至已有的缓冲区中。
*/
if((pid = fork())<0) err_sys("fork error");
if(pid == 0) //子进程
{
glob_var++;
var++;
}
else sleep(2); //让子进程先 运行 。 父子进程运行顺序不确定 取决于内核调度算法
//printf("after fork\n"); //若放在fork 后出现 两次 。
printf("pid is %d var is %d glob_var is %d\n",getpid(),var,glob_var);
exit(0);
}
8.4vfork
Vfork函数用于创建一个新进程 ,而该新进程的目的是exec一个新程序 .它并不将父进程的地址空间完全复制到子进程中 ,因为子进程会立即调用exec .
Vfork和fork的一个区别是 :vfork保证子进程先运行(这段时间内核会使父进程休眠) ,它在调用exec或exit之后父进程才可能被调度运行 .
/*************************************************************
*vfork 创建一个新进程 ,而该新进程 的目的是 exec 一个新程序
*它不将 父进程 空间完全 复制到子进程 因为 自己成会立刻调用 exec 或exit
*
*vfork 保证 子进程 先运行 ,它调用exec 或exit 之后 父进程 才可能被调度运行
*************************************************************/
#include <apue.h>
#include<myerr.h>
int glob_var =66;
int main()
{
int var = 6;
pid_t pid;
printf("before fork\n");/*
*此处若重定向普通文件也不会 出现两次了
*/
if((pid = vfork())<0) err_sys("vfork error");
if(pid == 0) //子进程
{
glob_var++;
var++;
_exit(0); //不冲洗缓冲区
}
printf("pid is %d var is %d glob_var is %d\n",getpid(),var,glob_var);
/*
*会发现 子进程 改变了父进程的 变量值
*因为 子进程在 父进程 地址空间运行。
*/
exit(0);
}
8.6 wait 和 waitpid
一个进程正常或异常终止时 ,内核就向其父进程发送SIGCHILD信号 .因为子进程终止是个异步事件 ,所以这种信号也是内核向父进程发的异步通知 .
调用wait和waitpid会发生什么 ;
1. 如果所有子进程都还在运行 ,则阻塞 .
2. 若一个子进程已经终止 ,正等待父进程获取其终止状态 ,则取得该子进程的终止状态立即返回 .
3. 如果没有任何子进程 ,则立即出错返回 .
/*******************************************************************************
*wait/waitpid
*1.若所有进程还在运行 ,在阻塞
*2.若一个子进程终止,正等待父进程获取其终止状态,则取得该子进程终止状态 立即返回
*3.若没任何子进程,则立即出错返回
*
*两者区别 :
*1.(waitepid 有一选项可使调用者不阻塞)
*2.waitpid 不等其调用之后的第一个终止子进程 它有若干选项可以控制它所有等待进程、
********************************************************************************/
#include<apue.h>
#include<sys/wait.h>
#include<myerr.h>
#define WCOREDUMP
void pre_exit(int status) //打印exit状态说明
{
if(WIFEXITED(status)) printf("nomal termination ,exit staus = %d\n",WEXITSTATUS(status));
else if(WIFSIGNALED(status)) printf("abnormal termination , signal number = %d%s\n",WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? "(core file generated)" : "");
#else
"");
#endif
else if(WIFSTOPPED(status)) printf("child stopped, signal number = %d\n,",WSTOPSIG(status));
}
int
main(void)
{
pid_t pid;
int status;
if((pid = fork())<0) err_sys("fork error");
else if(pid==0) exit(7); //WEXITSTATUS 获取子进程传送给exit参数的低8位
if(wait(&status)!=pid)
err_sys("wait error");
else pre_exit(status);
if((pid = fork())<0) err_sys("fork error");
else if(pid==0) abort(); //产生异常信号
if(wait(&status)!=pid)
err_sys("wait error");
else pre_exit(status);
if((pid = fork())<0) err_sys("fork error");
else if(pid==0) status/=0; //除0产生 SIGFPE
if(wait(&status)!=pid)
err_sys("wait error");
else pre_exit(status);
exit(0);
}
/****************************************************************
*waitpid(pid_t pid ,int *statloc, int option)
*pid ==-1 等待任一子进程 等效于 wait
*pid >0 等待进程id 与 pid 相等的子进程
*pid ==0 等待组 ID 等于调用进程组ID的任一进程
*pid <-1 等待组 ID 等于pid 绝对值的任一进程
*
*waitpid 返回终止子进程ID , 并将其终止状态 存放statloc 所指存储单元
*
*options: 进一步的控制操作 。详 见书193
****************************************************************/
#include <myerr.h>
#include <sys/wait.h>
int
main(void)
{
pid_t pid;
printf("1 pid = %ld \n",(long)getpid());
if((pid = fork())<0) err_sys("fork error");
else if(pid==0)
{
printf("2 pid = %ld \n",(long)getpid());
if((pid=fork())<0) err_sys("fork_2 error");
if(pid>0) //子进程fork 后 pid 为其子进程 ID 故>0 终止它 。
exit(0);
else
{
sleep(2); //休眠2秒确保 其 父进程 终止 否则 可能返回 其父进程 ID 而不是 init 的ID 1
printf("second child, parent pid = %ld \n",(long)getppid());
}
}
if(waitpid(pid,NULL,0)!=pid) err_sys("waitpid error");
exit(0); //注意 : 子进程的 子进程 会休眠两秒 故本程序进程会先终止 返回到 shell
}
8.9竞争条件 :
当多个进程都企图对共享数据进行某种处理 ,而最后结果又取决于进程运行顺序时 我们认为发生了竞争条件 .
8.10exec
当进程调用一种 exec函数时 ,该进程执行的程序完全替换为新程序 ,而新程序则从其main函数开始执行 .因为exec并不创建新进程 ,所以前后的进程ID并未改变 ,exec只是用磁盘上的一个新程序替换了当前进程的正文段 ,数据段 ,堆栈段 .
#include <myerr.h>
#include <sys/wait.h>
char *env_init[] = {"USER=unknown","PATH=/tmp",NULL};
int main(void)
{
pid_t pid;
if((pid=fork())<0) err_sys("fork error");
else if(pid == 0)
{
if(execle("./echoall","echoall","myarg1","myarg2",(char *)0,env_init )<0) err_sys("execle error");
}
if((waitpid(pid,NULL,0))<0) err_sys("waitpid error");
if((pid=fork())<0) err_sys("fork error");
else if(pid == 0)
{
if(execlp("echoall","echoall","only 1 arg",(char *)0)<0) err_sys("execle error");
}
exit(0);
}
static void chartime(char *str)
{
char *ptr;
int c;
setbuf(stdout,NULL); //NULL 关闭缓冲 使内核竟可能多次的在两个进程间进行切换。
for(ptr = str; (c= *ptr)!=0;ptr++)
{
putc(c,stdout);
}
}
8.11更改用户ID和组ID .