第6章
这章讲进程、虚拟内存和环境变量等。
进程是一个可执行程序的实例。一个程序可以创建很多进程。
进程是由内核定义的抽象实体,内核为此实体分配执行程序所需的系统资源。
从内核的角度来看,进程是由用户内存空间和内核数据结构组成的。程序的代码和代码中的变量存放在用户内存空间,内核数据结构用于维护进程状态信息。
对于每个进程都有一个唯一的进程号(进程ID)(正数),用来标识系统中的某个程序。
getpid(),返回调用该函数的进程的进程ID。
#include <unistd.h> pid_t getpid(void);
总是成功调用并返回调用该函数的进程的进程ID。
getppid(),返回自己的父进程的进程ID。
#include <unistd.h> pid_t getppid(void);
总是成功调用并返回该函数的父进程的ID。
PS:所有进程的始祖为init进程(进程ID为1)。
内存布局:
Linux/x86-32进程内存结构如下:
文本段存放着程序运行的机器语言指令,具有只读属性,防止进程通过错误指针修改自身指令。
初始化数据段存在着显式初始化的全局变量和静态变量。
未初始化数据段存放着未进行显式初始化的全局变量和静态变量。将已经初始化的全局变量和静态变量与未进行初始化的全局变量和静态变量分开存放是因为程序在存放在磁盘上没有必要为未初始化的变量分配存储空间,只需要记录未初始化数据段的位置和大小,在程序运行的时候再分配空间。
堆(heap)是在运行时为变量动态进行内存分配的区域,堆顶叫program break,后面一章就是通过brk()和sbrk()来调整堆的大小进而来分配内存(malloc()就是基于这两个函数实现)。
栈(stack)由栈帧组成的,可以动态增长和收缩的段,系统为每个当前调用的函数分配一个栈帧,里面存储了函数的局部变量(自动变量)。
通过虚拟内存管理技术可以高效的使用CPU和RAM资源。因为大多数都有两个局部性:空间局部性和时间局部性。
虚拟内存将每个程序使用的内存分割成小块、固定大小的页单元。对于RAM,将其划分成一系列与虚拟内存页面大小相同的页帧。在程序运行的时候,只有一部分的页驻留在物理内存页帧中,当进程需要访问的页面不在物理内存中,就会发生页面错误,并挂起进程的执行,再将页面从磁盘载入到物理内存。
内核为每个进程维护一张页表,通过页表可以虚拟地址对应的物理地址。
虚拟内存的实现需要硬件有分页内存管理单元(将虚拟内存地址转成相应的物理内存地址)。
通过虚拟内存管理可以将虚拟地址空间与RAM物理地址空间隔开:使进程与进程、进程与内核相互隔离,可以保护进程和内核的内存;可以使得多个进程共享内存;实现内存保护机制;程序员和一些程序无需关注程序在物理内存的布局;还可以提高CPU利用率。
命令行参数:
int argc; 命令行参数个数;
char *argv[];指向命令行参数的指针数组;
PS:这两个参数是main()函数的局部变量,所以其他函数要使用这两个参数需要进行传递或者设置一个指向argv的全局变量。
环境列表:
每个进程都有与自己相关的环境列表。
每条记录的形式为name=value;
新进程创建时会继承父进程的环境副本(原始的进程间通信方式)。
可以通过printenv输出当前的环境列表:
lancelot@debian:~$ printenv
SSH_AGENT_PID=
CLUTTER_IM_MODULE=ibus
GPG_AGENT_INFO=/home/lancelot/.cache/keyring-A5CMa0/gpg::
TERM=xterm
SHELL=/bin/bash
XDG_SESSION_COOKIE=66aede8f90c2ace983c1e18451c8875a-1397554660.783480-
HUSHLOGIN=FALSE
GNOME_KEYRING_CONTROL=/home/lancelot/.cache/keyring-A5CMa0
LC_ALL=zh_CN.UTF-
USER=lancelot
LIBGL_DRIVERS_PATH=/usr/lib/i386-linux-gnu/dri:/usr/lib/x86_64-linux-gnu/dri
LS_COLORS=rs=:di=;:ln=;:mh=:pi=;:so=;:do=;:bd=;;:cd=;;:or=;;:su=;:sg=;:ca=;:tw=;:ow=;:st=;:ex=;:*.tar=;:*.tgz=;:*.arj=;:*.taz=;:*.lzh=;:*.lzma=;:*.tlz=;:*.txz=;:*.zip=;:*.z=;:*.Z=;:*.dz=;:*.gz=;:*.lz=;:*.xz=;:*.bz2=;:*.bz=;:*.tbz=;:*.tbz2=;:*.tz=;:*.deb=;:*.rpm=;:*.jar=;:*.war=;:*.ear=;:*.sar=;:*.rar=;:*.ace=;:*.zoo=;:*.cpio=;:*.7z=;:*.rz=;:*.jpg=;:*.jpeg=;:*.gif=;:*.bmp=;:*.pbm=;:*.pgm=;:*.ppm=;:*.tga=;:*.xbm=;:*.xpm=;:*.tif=;:*.tiff=;:*.png=;:*.svg=;:*.svgz=;:*.mng=;:*.pcx=;:*.mov=;:*.mpg=;:*.mpeg=;:*.m2v=;:*.mkv=;:*.webm=;:*.ogm=;:*.mp4=;:*.m4v=;:*.mp4v=;:*.vob=;:*.qt=;:*.nuv=;:*.wmv=;:*.asf=;:*.rm=;:*.rmvb=;:*.flc=;:*.avi=;:*.fli=;:*.flv=;:*.gl=;:*.dl=;:*.xcf=;:*.xwd=;:*.yuv=;:*.cgm=;:*.emf=;:*.axv=;:*.anx=;:*.ogv=;:*.ogx=;:*.aac=;:*.au=;:*.flac=;:*.mid=;:*.midi=;:*.mka=;:*.mp3=;:*.mpc=;:*.ogg=;:*.ra=;:*.wav=;:*.axa=;:*.oga=;:*.spx=;:*.xspf=;:
DESKTOP_AUTOSTART_ID=10dc1ad7011f06891f139755466392940400000043790001
SSH_AUTH_SOCK=/home/lancelot/.cache/keyring-A5CMa0/ssh
SESSION_MANAGER=local/debian:@/tmp/.ICE-unix/,unix/debian:/tmp/.ICE-unix/
PATH=/home/lancelot/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/lancelot/bin
MAIL=/var/mail/lancelot
LC_COLLATE=C
PWD=/home/lancelot
XMODIFIERS=@im=ibus
LANG=zh_CN.UTF-
HOME=/home/lancelot
SHLVL=
LANGUAGE=zh_CN.UTF-:zh:en_US:en
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
LOGNAME=lancelot
XDG_DATA_DIRS=/usr/share/gnome:/usr/local/share/:/usr/share/
QT4_IM_MODULE=ibus
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-q3rPwh3OMI,guid=5495fcf0de26e09259bc54ef534cfde4
WINDOWPATH=
TEXTDOMAIN=im-config
DISPLAY=:
GTK_IM_MODULE=ibus
TEXTDOMAINDIR=/usr/share/locale/
XAUTHORITY=/home/lancelot/.Xauthority
_=/usr/bin/printenv
getenv(),获得进程环境的值
#include <stdlib.h> char *getenv(const char *name);
对于存在name的环境变量就返回对应的value的指针,如果不存在就返回NULL
putenv(),修改环境变量
#include <stdlib.h> int putenv(char *string);
将name=value形式的字符串添加到当前的环境列表。成功返回0,失败返回非0值。
PS:putenv是将environ变量的某个元素指向string指向的位置,并不是指向string指向字符串的副本。所以如果修改string指向的内容,环境变量也会改变,因此string指向后来不能是自动变量。
PS:当string里不包含等号的时候,将会在环境列表移除string命名的环境变量。
setenv(),往环境添加变量
#include <stdlib.h> int setenv(const char *name, const char *value, int overwrite);
name,value与name=value对应,overwrite决定时候改变环境。如果以name标识的变量存在环境里而且overwrite为0,就不改变环境;否则如果overwrite不为0就总是改变环境。
成功返回0, 失败返回-1
PS:不同与putenv,setenv是分配一块内存缓冲区,将name和value指向的字符串复制到缓冲区里。
unsetenv(),移除环境变量
#include <stdlib.h> int unsetenv(const char *name);
移除name标识的变量
成功调用返回0,失败返回-1。
clear(),清除整个环境
#define _BSD_SOURCE
#include <stdlib.h> int clearenv(void);
成功调用返回0,失败返回非0值。
PS:setenv和clearenv会导致内存泄露:setenv分配了一块内存缓冲区,但是clearenv没有释放这个缓冲区。
-----------------省略setjmp()和longjump(),非局部跳转(不同于goto,可以从一个函数跳转到另一个函数)------------
自己比较少使用。。。。还有整整一本下册啊。。。今天面试被虐了。。。
要继续认真看这本书。。。后台开发不容易做啊。。。。。。。。。。。。
----------------中间吐槽完毕-------------------------------------------------------------------------------------------------------------
练习:
6-1:编译程序清单6-1的程序,使用ls -l 命令显示可执行文件的大小。虽然程序包含一个大约10MB的数组,但可执行文件大小远小于此,为什么?
程序清单如下:
/*
* =====================================================================================
*
* Filename: mem_segment.c
*
* Description:
*
* Version: 1.0
* Created: 2014年03月19日 00时03分10秒
* Revision: none
* Compiler: gcc
*
* Author: alan (), alan19920626@gmail.com
* Organization:
*
* =====================================================================================
*/ #include <stdio.h>
#include <stdlib.h> char globBuf[];
int primes[] = {, , , }; static int square(int x){
int result; result = x * x;
return result;
} static void doCal(int val){
printf("The square of %d is %d\n", val, square(val)); if(val < ){
int t; t = val * val * val;
printf("The cube of %d is %d\n", val, t);
}
} int main(int argc, char *argv[]){
static int key = ;
static char mbuf[];
char *p; p = (char *)malloc(); doCal(key); exit(EXIT_SUCCESS);
}
6-1
结果如下:
lancelot@debian:~/Code/tlpi$ gcc -o mem_segments mem_segment.c
lancelot@debian:~/Code/tlpi$ gvim mem_segment.c
lancelot@debian:~/Code/tlpi$ ls -l mem_segments
-rwxr-xr-x lancelot lancelot 4月 : mem_segments
因为那个10MB的数组(静态)没有初始化,所以是存放在未初始化的数据,只有在运行的时候再为其分配内存。
6-3:使用getenv()函数,和putenv()函数,必要时可以直接修改environ,来实现setenv()函数和unsetenv()函数。(结合6-4程序清单)
/*
* =====================================================================================
*
* Filename: 6-3.c
*
* Description:
*
* Version: 1.0
* Created: 2014年04月11日 15时30分32秒
* Revision: none
* Compiler: gcc
*
* Author: alan (), alan19920626@gmail.com
* Organization:
*
* =====================================================================================
*/ #define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include "tlpi_hdr.h" extern char **environ;
char env[]; int M_setenv(const char *name, const char *value, int overwrite){
strcpy(env, name);
strcat(env, "=");
strcat(env, value); if(getenv(name) != NULL && overwrite == )
return ;
if(putenv(env) == )
return ;
else
return -;
} int M_unsetenv(const char *name){
if(putenv((char *)name) == )
return ;
return -;
} int main(int argc, char *argv[]){
int j;
char **ep; clearenv(); for(j = ; j < argc; j++)
if(putenv(argv[j]) != )
errExit("putenv: %s", argv[j]); if(M_setenv("GREET", "Hello world", ) == -)
errExit("M_setenv"); M_unsetenv("BYE"); for(ep = environ; *ep != NULL; ep++)
puts(*ep); exit(EXIT_SUCCESS);
}
6-4程序清单:
/*
* =====================================================================================
*
* Filename: modify_env.c
*
* Description:
*
* Version: 1.0
* Created: 2014年03月19日 10时48分04秒
* Revision: none
* Compiler: gcc
*
* Author: alan (), alan19920626@gmail.com
* Organization:
*
* =====================================================================================
*/ #define _GNU_SOURCE
#include <stdlib.h>
#include "tlpi_hdr.h" extern char **environ; int main(int argc, char * argv[]){
int j;
char **ep; clearenv(); for(j = ; j < argc; j++)
if(putenv(argv[j]) != )
errExit("putenv: %s", argv[j]); if(setenv("GREET", "Hello world", ) == -)
errExit("setenv"); unsetenv("BYE"); for(ep = environ; *ep != NULL; ep++)
puts(*ep); exit(EXIT_SUCCESS);
}
结果:
lancelot@debian:~/Code/tlpi$ ./- "GREET=Guten" SHELL=/bin/bash BYE=Ciao
GREET=Guten
SHELL=/bin/bash
lancelot@debian:~/Code/tlpi$ ./- SHELL=/bin/sh BYE=byebye
SHELL=/bin/sh
GREET=Hello world
PS:吐槽。。。。。。还要继续努力,努力成为一个后台开发。。。。。。。。。