进程环境与进程控制

 进程环境与进程控制(1): 进程的开始与终止

1. 进程的开始:
C程序是从main函数开始执行, 原型如下:
int main(int argc, char *argv[]);
通常main的返回值是int型, 正确返回0.
如果main的返回值为void或者无, 某些编译器会给出警告, 此时main的返回值通常是0.

关于main的命令行参数不做过多解释, 以下面的程序展示一下:

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;
    for (i = 0; i < argc; i++)
        printf("argv[%d]: %s/n", i, argv[i]);
    return 0;
}

 

 

2. 进程终止:
C程序的终止分为两种: 正常终止和异常终止.
正常终止分为: return, exit, _exit, _Exit, pthreade_exit
异常中指分为: abort, SIGNAL, 线程响应取消
主要说一下正常终止的前4种, 即exit系列函数.
#include <stdlib.h>   /* ISO C */
void exit(int status);
void _Exit(int status);
#include <unistd.h>   /* POSIX */
void _exit(int status);

以上3个函数的区别是:
exit()(或return 0)会调用终止处理程序和用户空间的标准I/O清理程序(如fclose), _exit和_Exit不调用而直接由内核接管进行清

理.
因此, 在main函数中exit(0)等价于return 0.

3. atexit终止处理程序:
ISO C规定, 一个进程最对可登记32个终止处理函数, 这些函数由exit按登记相反的顺序自动调用. 如果同一函数登记多次, 也会被

调用多次.
原型如下:
#include <stdlib.h>
int atexit(void (*func)(void));
其中参数是一个函数指针, 指向终止处理函数, 该函数无参无返回值.

以下面的程序为例:

#include <stdlib.h>

static void myexit1()
{
    printf("first exit handler/n");
}

static void myexit2()
{
    printf("second exit handler/n");
}

int main()
{
    if (atexit(my_exit2) != 0)
        printf("can't register my_exit2/n");
    if (atexit(my_exit1) != 0)
        printf("can't register my_exit1/n");
    if (atexit(my_exit1) != 0)
        printf("can't register my_exit1/n");

    printf("main is done/n");
    return 0;
}

 

 

1.  C/C++编译的程序, 内存分为以下几个部分(从低地址到高地址):
代码(text): 在可用内存的最低地址区, 存放程序函数的二进制代码和程序中所使用的常量, 函数调用是通过函数地址实现的.

全局已初始化数据(initialized data): 包括已初始化变量和已初始化的静态变量, 程序结束后自动释放.

全局未初始化数据(uninitialized data): 包括未初始化变量和未初始化的静态变量, 编译器会在编译器为这些变量初始化为0, 程序结束后自动释放.

棧(stack): 棧底在内存的高地址区, 由高地址向低地址增长. 棧由编译器负责申请和释放, 主要存放函数的参数, 局部变量等. 通常来说, 进程的棧容量很有限, 棧的生存周期有限, 这正是可以自动释放的原因.
如: 存在一个class Student, 创建一个局部对象Student st;
这个对象会在生存周期结束时自动调用析构函数, 释放该对象所占内存.

堆(heap): 堆在内存的低地址区, 由低地址向高地址增长. 堆由程序员手动申请和释放, 如果申请的内存不释放, 会造成内存泄露. 同时优点是我们可以决定堆内变量(对象)存在的时间, 对堆的操作对程序员是可见的, 只要我们保证释放就可以了. 注意: 一般情况下, 哪里申请的内存就要在哪里释放, 也就是说不要在A函数内申请的内存在B函数内释放, 这样是很危险的.

命令行参数和环境变量: 这在可用内存的最高地址区, 存储包括命令行参数, 环境变量等, 自动释放.

2. 示例代码:
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456/0在常量区(text),p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456/0放在常量区(text),编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


3. 堆和棧各自的优缺点:
堆的分配没有大小限制, 在32位系统上的限制是4GB;
而棧的大小是有限制的, 一般只能到几MB大小.

棧的分配效率很高,而且不会造成内存碎片;
而堆的分配效率不高, 大量的进行new/delete操作会造成大量的内存碎片.

棧中内存的释放是由编译器自己处理的;
而堆中内存的释放是由程序员手工处理的, 如果不释放会造成内存泄露.

构建在内存池管理上的程序可以优化这点, 它会把delete之后的内存保存在表里, 当需要new的时候先去表中查看, 如果有合适的空间就用, 如果没有合适的再进行new操作. 关于内存池这门很复杂的技术, 包括它的申请顺序, 大小匹配, 检索方法和回收机制, 以后有机会的话我会写相关的文章.

4. 小结:
在很多BBS流行着这样一句话,
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。


 

 

 

当父进程通过fork创建了子进程之后, 父进程和子进程的执行顺序是无法控制的. 如果想控制, 有两种办法: 使用vfork创建或者父进程调用等待函数wait.
关于fork和vfork的区别, 请参考APUE, 以后也我也会写相关的文章具体解释.
这篇文章主要介绍wait和类似的waitpid调用.

1. 当进程终止时, 会向其父进程发送SIGCHLD信号, 这个异步事件可以在父进程运行的任何时候发生, 包括正常和异常终止两种. 调用wait和waitpid的进程可能会有以下三种情况:
  1. 阻塞(如果其所有的子进程都还在运行)
  2. 带子进程的终止状态正常返回(其中一个子进程终止)
  3. 出错返回(没有子进程)
原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
函数返回: 若成功则为进程ID,若出错则为-1.
参数说明:
statloc: 如果是不是空指针, 则子进程的返回状态保存在该指针指向的整型变量中; 如果是空指针, 则忽             略返回状态.



2. waitpid函数:
如果一个进程有若干个子进程, 那么只要有一个子进程返回, wait就返回. 如果要等待一个指定的子进程, 有两种方法. 第一个种, 早期的UNIX必须调用wait, 然后把返回的pid和期望pid做比较, 如果不是期望的, 把该pid保存起来, 继续调用wait, 直到进程终止. 第二种是使用waitpid.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);

函数返回: 若成功则为进程ID, 若出错则为-1.
参数说明:
pid:
  • pid == -1 等待任一子进程. 这个时候waitpid与wait等效.
  • pid > 0     等待其ID与pid相等的子进程.
  • pid == 0  等待其组ID与调用进程的组ID的任一子进程.
  • pid < -1    等待其组ID等于pid绝对值的任一子进程.
对于waitpid函数, 如果指定的进程或进程组不存在, 或者调用进程没有子进程都会出错.

options:
这个一参数可以让我们进一步控制waitpid的操作, 此参数或者是0, 或者是下列的逐位或常数之一:
WNOHANG: 若由pid指定的子进程并不立即可用, 则waitpid不阻塞, 此时其返回值为0.
WUNTRACED: 若某实现支持作业控制, 则由pid指定的任一子进程状态已暂停, 且其状态自暂停以来还                       未报告过, 则返回其状态. WIFSTOPPED宏确定返回值是否对应于一个暂停子进程.

3. waitpid和wait的区别:
waitpid提供了wait函数不能实现的3个功能:
  • waitpid等待特定的子进程, 而wait则返回任一终止状态的子进程;
  • waitpid提供了一个wait的非阻塞版本;
  • waitpid支持作业控制(以WUNTRACED选项).

4. 用于检查wait和waitpid两个函数返回终止状态的宏:
这两个函数返回的子进程状态都保存在statloc指针中, 用以下3个宏可以检查该状态:
  • WIFEXITED(status): 若为正常终止, 则为真. 此时可执行
    • WEXITSTATUS(status): 取子进程传送给exit或_exit参数的低8位.
  • WIFSIGNALED(status): 若为异常终止, 则为真. 此时可执行
    • WTERMSIG(status):  取使子进程终止的信号编号.
  • WIFSTOPPED(status): 若为当前暂停子进程, 则为真. 此时可执行
    • WSTOPSIG(status): 取使子进程暂停的信号编号.

具体实例代码请参考APUE.

1. 函数声明:
#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[]);

六个函数返回: 若出错则为- 1,若成功则不返回.

这些函数之间的第一个区别是前四个取路径名作为参数, 后两个则取文件名作为参数. 当指定filename作为参数时:
• 如果filename 中包含/, 则就将其视为路径名.
• 否则就按PATH环境变量, 在有关目录中搜寻可执行文件.
第二个区别与参数表的传递有关(l表示表(list), v表示矢量(vector)). 函数execl, execlp和execle要求将新程序的的每个命令行参数都说明为一个单独的参数. 这种参数表以空指针结尾.
对于另外三个函数execv, execvp和execve则应先构造一个指向各参数的指针数组, 然后将该数组地址作为这三个函数的参数.

2. 这六个函数中, 只有execve是系统调用, 其他函数只是库函数, 各种不同的实现, 最终还是要调用execve的.
execl的三个函数都是通过构造argv调用execv的三个不同函数, 而execvp试path前缀调用execv, execv使用环境变量调用execve系统调用.

3. exec函数主要用于fork之后子进程调用, 实现多进程操作.
#include <sys/types.h>
#include <sys/wait.h>

char *env[] = {"USER=unknown", "PATH=/tmp", NULL};

int main()
{
    pid_t pid;
   
    if ((pid = fork()) < 0)
        /* Error Handler */
    else if (pid == 0)
        execle...
   
    if (waitpid(pid, NULL, 0) < 0)
        /* Error Handler */

    if ((pid = fork()) < 0)
        /* Error Handler */
    else if (pid == 0)
        execlp...

    return 0;
}
本程序中, execle要求的参数是一个程序路径和一个自定义的特定环境;
execlp要求的参数是一个文件名, 并接收系统环境变量, 这里的调用会继承前面execle添加的环境.

4. exec注意事项:
最后一个命令行参数之后要跟一个空指针, 如果是0, 要强制转换成(char *), 不然会报错.

5. 方便记忆:
为了使这6个函数方便记忆, 下面用函数名中的字符说明一下:
l: 表示该函数取一个参数表, 与v互斥.
v: 表示该函数取一个argv[]矢量.
e: 表示该函数取一个envp[]数组, 而不使用当前环境变量.
p: 表示该函数取filename作为参数, 并且用PATH环境变量寻找可执行文件.


<script type="text/javascript">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>
#include  < sys / times.h >
clock_t times(
struct  tms  * buf);

返回值: 若成功则返回流逝的墙上时钟时间, 若出错返回-1.
所有由此函数返回的clock_t值都用_SC_CLK_TCK变换成秒数.(由sysconf函数返回的每秒时钟滴答数)
此函数填写由buf指向的tms结构, 该结构定义如下:

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

注意: 此结构没有包含墙上时钟是的任何测量值. 作为替代, times函数返回墙上时钟时间作为函数值. 此值是相对于过去的某一时刻测试的, 所以不能用其绝对值, 而必须使用其相对值.
例如, 调用times, 保存其返回值. 在以后某个时间再次调用times, 从新的返回值中减去以前的返回值, 此差值就是墙上时钟时间.
该结构中的后两个成员是针对子进程的变量, 包含了用wait, waitpid等待到的各个子进程的值.

 

实例代码:

#include  < sys / times.h >

static   void  pr_times(clock_t, struct tms  * , struct tms  * );
static   int  do_cmd( char   * );

int  main()
{
    
int i;
    setbuf(stdout, NULL);
    
for (i = 1; i < argc; i++)
        do_cmd(argv[i]);
    
    
return 0;
}


static   int  do_cmd( char   * cmd)
{
    struct tms tmsstart, tmsend;
    clock_t start, end;
    
int status;

    printf(
" command: %s ", cmd);

    
if ((start = times(&tmsstart)) == -1)
        
/* error handler */

    
if ((status == system(cmd)) < 0)
        
/* error handler */

    
if (end = times(&tmsend)) == -1_
        
/* error handler */

    pr_times(end
-start, &tmsstart, &tmsend);

    
return status;
}


static   void  pr_times(clock_t real, struct tms  * tmsstart, struct tms  * tmsend)
{
    
static long clktck = 0;

    
if (clktck == 0)
        
if ((clktck = sysconf(_SC_CLK_TCK)) < 0)
            
/* error hanlder */
    printf(
" read: %7.2f ", read / (double)clktck);
    printf(
" user: %7.2f ", tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
    ...
    ...
}

运行:


 

$ ./a.out "sleep 5" "date"

command: sleep 5
 real:      5.02
 user:      0.00
 sys:       0.00
 child user:      0.01
 child sys:       0.00

command: date
Fri Jun 22 17:11:20 EST 2007
 real:      0.01
 user:      0.00
 sys:       0.00
 child user:      0.01
 child sys:       0.00

 

转载与http://blog.csdn.net/dai_weitao/

南京捷帝科技 www.jiedichina.com

运行结果:
$ ./a.out
main is done
first exit handler
first exit handler
second exit handler
运行结果:
$./a.out arg1 arg2 arg3
argv[0]: ./a.out
argv[1]: arg1
argv[2]: arg2
argv[3]: arg3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏曹俊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值