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节会更详细介绍进程内存布局。
可以通过将上述代码中的内容只保留main函数其余注释掉之后编译,size 查看结构,然后在取消注释之后,逐步查看size的不同,进而确定该类型变量在内存中的布局.
每条记录的形式为name=value;
新进程创建时会继承父进程的环境副本(原始的进程间通信方式。
可以通过printenv输出当前的环境列表:
在linux系统中通过查看/proc/进程PID/environ文件可以查看相应的环境列表。
对于存在name的环境变量就返回对应的value的指针,如果不存在就返回NULL
将name=value形式的字符串添加到当前的环境列表。成功返回0,失败返回非0值。
putenv是将environ变量的某个元素指向string指向的位置,并不是指向string指向字符串的副本。所以如果修改string指向的内容,环境变量也会改变,因此string指向后来不能是自动变量。
当string里不包含等号的时候,将会在环境列表移除string命名的环境变量。
name,value与name=value对应,overwrite决定时候改变环境。如果以name标识的变量存在环境里而且overwrite为0,就不改变环境;否则如果overwrite不为0就总是改变环境。
成功返回0, 失败返回-1
不同与putenv,setenv是分配一块内存缓冲区,将name和value指向的字符串复制到缓冲区里。
移除name标识的变量
成功调用返回0,失败返回-1。
成功调用返回0,失败返回非0值。
setenv和clearenv会导致内存泄露:setenv分配了一块内存缓冲区,但是clearenv没有释放这个缓冲区。
例子:
命令行参数相关讲解参考文章C语言命令行参数
示例代码:
代码解析:
上面代码保存为longjmp.c,运行./longjmp此时argc为1,如果我输入./longjmp x argc不为1,上述代码setjmp只有在第一次执行时返回0,然后下次执行时通过longjmp跳转过来,通过longjmp设置的值就是setjmp返回的值,本例中f1中返回1,f2中返回2.
代码讲解:
gcc -o setjmp_vars setjmp_vars.c
gcc -O -o setjmp_vars setjmp_vars.c
-O选项是开启编译器优化,上述例子中如果不将局部变量定义为volatile的话在调用setjmp()的函数中会存在问题。所以为避免编译器优化所引发的问题,应使用volatile修饰符声明变量。
进程的虚拟内存逻辑上被划分为许多段:文本段、数据段、栈和堆。
栈由一系列帧组成,随函数调用而增长,随函数返回而减,每个帧都包含有函数局部变量、函数实参以及单个函数调用的调用链接信息。
程序调用时命令行参数通过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修饰符声明变量。
相关文章推荐
- TLPI-Chapter 9 进程凭证
- TLPI-Chapter 12系统和进程信息
- Chapter 13 守护进程
- TLPI-Chapter 13文件I/O缓冲
- OS X下UNIX环境高级编程(第三版)学习日志-第一章ChapterI,程序和进程
- chapter 4: 强杀进程(不到万不得已,千万不要使用)
- TLPI-Chapter 5深入探究文件
- Chapter 8 进程控制
- Linux -- Chapter 11 进程与信号
- UNP总结 Chapter 12~14 IPv4与IPv6的互操作性、守护进程和inet超级服务器、高级I/O函数
- Chapter 9 进程关系
- Linux -- Chapter 11 进程与信号
- C++并发编程实战chapter1你好,C++的并发世界--笔记0--多进程并发与多线程并发
- APUE 阅读笔记(chapter 13)——守护进程
- Chapter 8. 进程
- chapter 7之进程管理
- chapter 4.1 进程,编写第一个windows应用程序
- Understanding Unix/Linux Programming 笔记:chapter 11:连接到近端或远端的进程:服务器与Socket(套接字)
- TLPI-Chapter 10 时间
- Understanding Unix/Linux Programming 笔记:chapter 8:进程和程序:编写命令解释器sh