本章介绍Unix的进程控制,包括进程创建,执行程序和进程终止,进程的属性,exec函数系列,system函数,进程会计机制。

1、进程标识符

  每一个进程都有一个非负整数标识的唯一进程ID。ID为0表示调度进程,即交换进程,是内核的一部分,也称为系统进程,不执行任何磁盘操作。ID为1的进程为init进程,init进程不会终止,他是一个普通的用户进程,需要超级用户特权运行。获取标识符函数如下:

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);    //调用进程的进程ID
pid_t getppid(void);     //调用进程的父进程ID
gid_t getgid(void);       //调用进程的实际组ID
gid_t getegid(void);    //调用进程的有效组ID
uid_t getuid(void);     //调用进程的实际用户ID
uid_t geteuid(void);   //调用进程的有效用户ID

2、fork函数

  一个现有的进程可以调用fork函数创建一个新进程。函数原型为:pid_t
fork(void)。有fork创建的新进程称为子进程,fork函数调用一次返回两次。子进程的返回值为0,父进程的返回值为新子进程的ID。子进程和父进程举行执行fork调用后的指令,子进程是父进程的副本。子进程获得父进程的数据空间、栈和队的副本,父子进程不共享这些存储空间部分,共享正文段。子进程相当于父进程克隆了一份自己。 创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

  fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。

注意的是:父进程设置的文件锁不会被子进程继承。

写个程序,创建一个子进程,在子进程中改变变量的值,然后父子进程同时输出变量的值,看看有什么变化,同时输出子进程的标识符信息。程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6 #include <errno.h>
7 //全局变量
8 int global = 100;
9 char buf[]="a write to stdout.\n";
10
11 int main()
12 {
13 int var;
14 pid_t pid;
15
16 var = 90;
17 write(STDOUT_FILENO,buf,sizeof(buf)-1);
18 //创建一个子进程
19 pid = fork();
20 if(pid == -1) //出错
21 {
22 perror("fork() error");
23 exit(-1);
24 }
25 if(pid == 0) //子进程
26 {
27 printf("This is child process.\n");
28 printf("Child process id is:%d\n",getpid());
29 printf("Father process id is:%d\n",getppid());
30 //子进程修改数据
31 global++;
32 var++;
33 }
34 if(pid > 0) //父进程
35 sleep(3); //等待子进程先执行
36 printf("pid = %d,ppid =%d,global=%d,var=%d\n",getpid(),getppid(),global,var);
37 return 0;
38 }
 程序执行结果如下:

从结果可以看出子进程拥有自己的数据空间,不与父进程共享数据空间。

  vfork函数的调用序列和返回值与fork相同,但是vfork并不将父进程的地址空间完全复制到子进程中,在调用exec和exit之前在父进程的空间中运行。vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

  进程终止最后都会执行内核中的同一段代码,为相应的进程关闭所有打开描述符,释放它所使用的存储器等。子进程可以通过exit函数通知父进程是如何终止的,父进程调用wait或waitpid函数可以获取终止状态。子进程是在父进程调用fork后产生的,如果父进程在子进程之前终止,则将子进程的父进程改变为init进程,保证每一个进程都有一个父进程。一个已经终止,但其父进程尚没有对其进行善后处理的进程称为僵死进程(zombie)。由init进程领养的子进程不会变成僵死进程,因为init进程在子进程终止的时候会调用一个wait函数取得子进程的终止状态。

  当一个进程正常或者异常终止的时,内核就像其父进程发送SIGCHLD信号。调用wait和waitpid函数可能发生的情况:(1)如果所有子进程都还在运行,则阻塞;(2)如果一个子进程已经终止,正等待父进程获取进程终止状态,则取得孩子的终止状态立刻返回;(3)若果没有任何子进程,则立即出错返回。如果在任意时刻调用wait,则进程可能会阻塞。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);   //在一个子进程终止前,wait使调用者阻塞

pid_t waitpid(pid_t pid, int *status, int options); //可以使调用者不阻塞

#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>
pid_t wait3(int *
status, int options,struct rusage *rusage);
pid_t wait4(pid_t
pid, int *status, int options, struct rusage *rusage);
写个程序,子进程给出退出状态,父进程通过wait和waitpid获取退出状态。程序如下:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6 #include <signal.h>
7
8 int main()
9 {
10 pid_t pid;
11 int status;
12 if((pid = fork()) == -1)
13 {
14 perror("fork() error");
15 exit(-1);
16 }
17 if(pid == 0)
18 exit(0);
19 if(wait(&status) == pid)
20 printf("child normal exit,exit status=%d\n",WEXITSTATUS(status));
21 if((pid = fork()) == -1)
22 {
23 perror("fork() error");
24 exit(-1);
25 }
26 if(pid == 0)
27 abort();
28 if(waitpid(pid,&status,0) == pid)
29 printf("child abnormal termination,signal number=%d\n",WTERMSIG(status));
30 }

程序执行结果如下:

waitpid函数中的pid参数取值情况:
pid=-1   等待任一子进程,此时相当于wait
pid>0     等待期进程ID与pid相等的子进程
pid==0    等待期组ID等于调用进程组ID的任一个子进程
pid<-1    等待其组ID等于pid绝对值的任一子进程

另外提供了一种避免僵死进程的方法:调用fork两次。

  exec函数,用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另外一个程序。当调用exec函数时,该进程执行的程序完全替换为新进程,exec函数不新建进程,只是用一个全新的程序替换了当前的正文、数据、堆和栈段。执行完之后,进程ID不会改变。在进程间通信的时候,经常需要调用exec函数启动另外一个例程。函数原型如下:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[],char *const envp[]);

其中l表示列表(list),v标识矢量(vector)。execl、execlp、execle中每个命令行参数都是一个单独参数,这种参数以空指针结尾。execv、execvp、execve命令行参数是一个指针数组。e标识环境变量,传递参数。写个程序进行测试,程序分为两部分,exec调用的程序,exec执行程序。程序如下:

exec调用程序如下,可执行文件名称为exectest,存放在/home/anker/Programs目录下。

 1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc,char *argv[])
5 {
6 int i;
7 char **ptr;
8 extern char **environ;
9
10 for(i=0;i<argc;++i)
11 printf("argv[%d]=%s\n",i,argv[i]);
12 for(ptr=environ;*ptr!=0;ptr++)
13 printf("%s\n",*ptr);
14 exit(0);
15 }

exec执行程序如下:存放在/home/anker/Programs目录下。

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h> char *env_init[] = {"LENGTH=100","PAHT=/tmp",NULL}; int main()
{
pid_t pid;
char *path = "/home/anker/Programs/exectest";
char *filename = "exectest";
char *argv[3]= {0};
argv[0] = "exec";
argv[1] = "test";
if((pid=fork()) == -1)
{
perror("fork() error");
exit(-1);
}
else if(pid == 0)
{
printf("Call execle:\n");
execle(path,argv[0],argv[1],(char*)0,env_init);
}
waitpid(pid,NULL,0); if((pid=fork()) == -1)
{
perror("fork() error");
exit(-1);
}
else if(pid == 0)
{
printf("Call execve:\n");
execve(filename,argv,env_init);
}
waitpid(pid,NULL,0);
exit(0); }

执行结果如下:

system函数,在程序中执行一个命令字符串。例如system("date > file")。函数原型如下:

#include <stdlib.h>
int system(const char *command);

system函数实现中调用了fork、exec和waitpid函数,因此有三种返回值。system函数实现一下,没有信号处理。程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h> int mysystem(const char * cmdstring); int main()
{
int status;
status = mysystem("date");
if(status < 0)
{
printf("mysystem() error.\n");
}
status = mysystem("who");
if(status < 0)
{
printf("mysystem() error.\n");
}
exit(0);
} int mysystem(const char * cmdstring)
{
pid_t pid;
int status; if(cmdstring == NULL)
return 1;
if((pid = fork()) == -1)
{
status = -1;
}
else if(pid == 0) //在子进程中调用shell脚本
{
execl("/bin/sh","sh","-c",cmdstring,(char *)0);
_exit(127);
}
else
{
while(waitpid(pid,&status,0) < 0)
{
if(errno != EINTR)
{
status = -1;
break;
}
}
}
return status;
}

程序执行结果如下:

system函数可以设置用户的ID,这是一个安全漏洞。

进程会计:启用后,每当进程结束时候内核就写一个会计记录,包括命令名、所使用的CPU时间总量、用户ID和组ID、启动时间等。accton命令启动会计处理,会计记录写到指定的文件中,Linux中位于/var/account/ pacct。会计记录结构定义在<sys/acct.h>头文件中。

#define ACCT_COMM 16

typedef u_int16_t comp_t;

struct acct {
    char ac_flag;           /* Accounting flags */
    u_int16_t ac_uid;       /* Accounting user ID */
    u_int16_t ac_gid;       /* Accounting group ID */
    u_int16_t ac_tty;       /* Controlling terminal */
    u_int32_t ac_btime;     /* Process creation time(seconds since the Epoch) */
    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 (kB) */
    comp_t    ac_io;        /* Characters transferred (unused) */
    comp_t    ac_rw;        /* Blocks read or written (unused) */
    comp_t    ac_minflt;    /* Minor page faults */
    comp_t    ac_majflt;    /* Major page faults */
    comp_t    ac_swaps;     /* Number of swaps (unused) */
    u_int32_t ac_exitcode;  /* Process termination status(see wait(2)) */
    char      ac_comm[ACCT_COMM+1];/* Command name (basename of last executed command; null-terminated) */
    char      ac_pad[X];    /* padding bytes */
};
用户标识,用getlogin函数获取用户的登录名。函数原型如下:char *getlogin(void)。

进程时间:墙上时钟时间、用户CPU时间和系统CPU时间。任一个进程都可以调用times函数获取它自己及已终止子进程时间。进程时间操作函数及结构如下:

#include <sys/times.h>

clock_t times(struct tms *buf);

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 */
};

总结:通过本章的学习。完全的了解Unix的进程控制,掌握了fork、exec簇、wait和waitpid进程控制函数。另外学习了system函数和进程会计。了解了解释器文件及其工作方式,用户标识和进程时间。

Unix环境高级编程(六)进程控制的更多相关文章

  1. UNIX环境高级编程--8. 进程控制

    进程控制进程标识:    每一个进程都有一个非负整型表示的唯一进程ID.虽然唯一,但是ID可以复用.当一个进程结束后,其进程ID会被延迟复用.    ID=0的进程通常是调度进程,常被称作交换进程(s ...

  2. UNIX环境高级编程--9. 进程控制

    进程关系    当子进程终止时,父进程得到通知并能取得子进程的退出状态. 终端登录:    早起UNIX系统通过哑终端登录,本地的终端 or 远程的终端 .主机上链接的终端设备是固定的,所以同时登录数 ...

  3. Unix环境高级编程——守护进程记录总结(从基础到实现)

    一.概念及其特征 守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程 ...

  4. Unix环境高级编程(八)进程关系

    本章看后给人似懂非懂的感觉,主要是不知道实际当中如何去使用.通过前面几章的学习,每个进程都有一个父进程,当子进程终止时,父进程得到通知并取得子进程的退出状态.先将本章基本的知识点总结如下,日后再看时候 ...

  5. Unix环境高级编程(五)进程环境

    本章主要介绍了Unix进程环境,包含main函数是如何被调用的,命令行参数如何传递,存储方式布局,分配存储空间,环境变量,进程终止方法,全局跳转longjmp和setjmp函数及进程的资源限制. ma ...

  6. UNIX环境高级编程——Linux进程地址空间和虚拟内存

    一.虚拟内存 分段机制:即分成代码段,数据段,堆栈段.每个内存段都与一个特权级相关联,即0~3,0具有最高特权级(内核),3则是最低特权级(用户),每当程序试图访问(权限又分为可读.可写和可执行)一个 ...

  7. UNIX环境高级编程——守护进程列表

    amd:自动安装NFS(网络文件系统)守侯进程apmd:高级电源治理Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库Autofs:自动安装治理进程automount ...

  8. UNIX环境高级编程——守护进程

    一.守护进程简介 守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系 ...

  9. (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. IOS系统之蓝牙外接设备

    Ios系统对于蓝牙外接设备在iphone4以前都是蓝牙2.0的时候,需要通过苹果的审核,据统计通过率仅有2%左右,现在蓝牙2.0基本上处于淘汰状态,所以在这里就不考虑了. 现在iphone4s以后的设 ...

  2. rcp(插件开发) The 'Eclipse-LazyStart' header is deprecated, use 'Bundle-ActivationPolicy'

    'Eclipse-LazyStart' 这个规范在3.4    osgi 4.1 以后就不支持了 大家使用Bundle-ActivationPolicy: lazy 这个属性来实现插件的懒加载吧!

  3. du 命令秘籍

    导读 du命令是检查硬盘使用情况,统计文件或目录及子目录使用硬盘的空间大小.参数的不同组合,可以更快的提高工作效率,以下仅列出了经常使用到的参数,如需更详细的信息,请用man du命令来获得. 1.命 ...

  4. Cognos两种建模工具对于复杂日期维度的处理比较(上)

    众所周知,在数据仓库中,日期维度是相当重要的.对数据分析的过程中可以从不同的角度去分析,比如按照下面的日期层次去分析数据. 年-季度-月-日 年-月-日 年-周-日 本示例将利用简单的商品销售分析的d ...

  5. (数据挖掘-入门-3)基于用户的协同过滤之k近邻

    主要内容: 1.k近邻 2.python实现 1.什么是k近邻(KNN) 在入门-1中,简单地实现了基于用户协同过滤的最近邻算法,所谓最近邻,就是找到距离最近或最相似的用户,将他的物品推荐出来. 而这 ...

  6. windows server 2012 IE增强的安全配置如何关闭

    http://jingyan.baidu.com/article/6181c3e076ac0b152ff15354.html 打开左下角的 服务端 关闭这个就可以了

  7. Lisp分配给保护的符号…

    在进行调试时,弹出"分配给保护的符号:pf,是否进入中断循环",但似乎不会影响结果. 明经版主解译说是:给受保护的符号重新赋值了,所以有此提示,此提示仅在打开了vlide 编辑器才 ...

  8. android 实现qq聊天对话界面效果

    效果图: chatting_item_from.xml <?xml version="1.0" encoding="UTF-8"?><Line ...

  9. Java高并发syncronized深入理解

    1.Synchronized的作用: 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果. 2.地位: 1)Synchronized是java的关键字,并java的怨言原生支持: ...

  10. iOS强引用和弱引用

    保留一个对象创建了一个对该对象的“强”引用.一个对象只有在它的所有强引用都被释放后才能被回收.因此,一个对象的生命周期取决于其强引用的所有者.在某些情况下,这种行为可能并不理想.您可能想要引用一个对象 ...