您的位置:首页 > 其它

TLPI-Chapter 6 进程

2017-10-08 00:00 330 查看
每个进程都有一个唯一进程标识号(process ID),并保存有对其父进程号的记录。

进程的虚拟内存逻辑上被划分为许多段:文本段、数据段、栈和堆。

栈由一系列帧组成,随函数调用而增长,随函数返回而减,每个帧都包含有函数局部变量、函数实参以及单个函数调用的调用链接信息。

程序调用时命令行参数通过argc argv参数提供给main()函数。通常argv[0]包含调用程序的名称。

每个进程都会获得其父进程环境列表的一个副本,即一组”名称-值“的键值对。全局变量environ和各种库函数允许进程访问和修改其环境列表中的变量。

setjmp()函数和longjmp()函数提供了从函数甲执行非局部跳转到函数乙的方法。在调用这些函数时,为避免编译器优化所引发的问题,应使用volatile修饰符声明变量。非局部跳转会使程序难以阅读和维护,应尽量避免使用。

进程是一个可执行程序的实例。一个程序可以创建很多进程。

进程是由内核定义的抽象实体,内核为此实体分配执行程序所需的系统资源。

从内核的角度来看,进程是由用户内存空间和内核数据结构组成的。程序的代码和代码中的变量存放在用户内存空间,内核数据结构用于维护进程状态信息。

对于每个进程都有一个唯一的进程号(进程ID)(正数),用来标识系统中的某个程序。

系统调用getpid()返回调用进程的进程号。

系统调用getppid()可以检索到父进程的进程号。

使用命令pstree可以查看进程树。

如果子进程的父进程终止,则子进程就会变成孤儿,Init进程随即收养该进程,子进程后续对getppid()调用将返回进程号1.

在Linux系统中可以查看/proc/进程PID/status中PPid字段获知该进程的父进程。

本章中介绍的进程内存布局可以参考文章C内存布局,本书48.5节会更详细介绍进程内存布局。

#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>

char globBuf[65536];            /* Uninitialized data segment */
int primes[] = { 2, 3, 5, 7 };  /* Initialized data segment */
mem_se
static int
square(int x)                   /* Allocated in frame for square() */
{
int result;                 /* Allocated in frame for square() */

result = x * x;
return result;              /* Return value passed via register */
}

static void
doCalc(int val)                 /* Allocated in frame for doCalc() */
{
printf("The square of %d is %d\n", val, square(val));

if (val < 1000) {
int t;                  /* Allocated in frame for doCalc() */

t = val * val * val;
printf("The cube of %d is %d\n", val, t);
}
}

int
main(int argc, char *argv[])    /* Allocated in frame for main() */
{
static int key = 9973;      /* Initialized data segment */
static char mbuf[10240000]; /* Uninitialized data segment */
char *p;                    /* Allocated in frame for main() */

p = malloc(1024);           /* Points to memory in heap segment */

doCalc(key);

exit(EXIT_SUCCESS);
}


可以通过将上述代码中的内容只保留main函数其余注释掉之后编译,size 查看结构,然后在取消注释之后,逐步查看size的不同,进而确定该类型变量在内存中的布局.

环境列表

每个进程都有与自己相关的环境列表。

每条记录的形式为name=value;

新进程创建时会继承父进程的环境副本(原始的进程间通信方式。

可以通过printenv输出当前的环境列表:

在linux系统中通过查看/proc/进程PID/environ文件可以查看相应的环境列表。

char *getenv(const char *name);


对于存在name的环境变量就返回对应的value的指针,如果不存在就返回NULL

int putenv(char *string);


将name=value形式的字符串添加到当前的环境列表。成功返回0,失败返回非0值。

putenv是将environ变量的某个元素指向string指向的位置,并不是指向string指向字符串的副本。所以如果修改string指向的内容,环境变量也会改变,因此string指向后来不能是自动变量。

当string里不包含等号的时候,将会在环境列表移除string命名的环境变量。

int setenv(const char *name, const char *value, int overwrite);


name,value与name=value对应,overwrite决定时候改变环境。如果以name标识的变量存在环境里而且overwrite为0,就不改变环境;否则如果overwrite不为0就总是改变环境。

成功返回0, 失败返回-1

不同与putenv,setenv是分配一块内存缓冲区,将name和value指向的字符串复制到缓冲区里。

int unsetenv(const char *name);


移除name标识的变量

成功调用返回0,失败返回-1。

int clearenv(void);


成功调用返回0,失败返回非0值。

setenv和clearenv会导致内存泄露:setenv分配了一块内存缓冲区,但是clearenv没有释放这个缓冲区。

例子:

#define _GNU_SOURCE     /* Get various declarations from <stdlib.h> */
#include <stdlib.h>
#include "tlpi_hdr.h"

extern char **environ;

int
main(int argc, char *argv[])
{
int j;
char **ep;

clearenv();         /* Erase entire environment */

/* Add any definitions specified on command line to environment */

for (j = 1; j < argc; j++)
if (putenv(argv[j]) != 0)
errExit("putenv: %s", argv[j]);

/* Add a definition for GREET if one does not already exist */

if (setenv("GREET", "Hello world", 0) == -1)
errExit("setenv");

/* Remove any existing definition of BYE */

unsetenv("BYE");

/* Display current environment */

for (ep = environ; *ep != NULL; ep++)
puts(*ep);

exit(EXIT_SUCCESS);
}


命令行参数相关讲解参考文章C语言命令行参数

执行非局部跳转setjmp()和longjmp()

setjmp()函数和longjmp()函数提供了从函数甲执行非局部跳转到函数乙的方式。

示例代码:

#include <setjmp.h>
#include "tlpi_hdr.h"

static jmp_buf env;

static void
f2(void)
{
longjmp(env, 2);
}

static void
f1(int argc)
{
if (argc == 1)
longjmp(env, 1);
f2();
}

int
main(int argc, char *argv[])
{
switch (setjmp(env)) {
case 0:     /* This is the return after the initial setjmp() */
printf("Calling f1() after initial setjmp()\n");
f1(argc);               /* Never returns... */
break;                  /* ... but this is good form */

case 1:
printf("We jumped back from f1()\n");
break;

case 2:
printf("We jumped back from f2()\n");
break;
}

exit(EXIT_SUCCESS);
}


代码解析:

上面代码保存为longjmp.c,运行./longjmp此时argc为1,如果我输入./longjmp x argc不为1,上述代码setjmp只有在第一次执行时返回0,然后下次执行时通过longjmp跳转过来,通过longjmp设置的值就是setjmp返回的值,本例中f1中返回1,f2中返回2.

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static jmp_buf env;

static void
doJump(int nvar, int rvar, int vvar)
{
printf("Inside doJump(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
longjmp(env, 1);
}

int
main(int argc, char *argv[])
{
int nvar;
register int rvar;          /* Allocated in register if possible */
volatile int vvar;          /* See text */

nvar = 111;
rvar = 222;
vvar = 333;

if (setjmp(env) == 0) {     /* Code executed after setjmp() */
nvar = 777;
rvar = 888;
vvar = 999;
doJump(nvar, rvar, vvar);

} else {                    /* Code executed after longjmp() */

printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
}

exit(EXIT_SUCCESS);
}


代码讲解:

gcc -o setjmp_vars setjmp_vars.c

gcc -O -o setjmp_vars setjmp_vars.c

-O选项是开启编译器优化,上述例子中如果不将局部变量定义为volatile的话在调用setjmp()的函数中会存在问题。所以为避免编译器优化所引发的问题,应使用volatile修饰符声明变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  进程