进程环境
c程序总是从main函数开始 执行,原型为: int main(int argc, char* argv[]);
内核执行c程序时(使用exec函数),调用main函数之前调用一个特殊的启动例程,可执行文件将启动例程设置为该程序的起始地址——这是由连接编辑器设置的,而连接编辑器是由c编译器调用。启动例程从内核中取得命令行参数和环境变量值,按上述方式调用main函数。
进程终止的方式有8种:
1.从main返回
2.调用exit
3.调用_exit或_Exit
4.最后一个线程从其他启动例程返回
5.从最后一个线程调用pthread_exit
三种异常终止方式:
6.调用abort
7.接到一个信号
8.最后一个线程对取消请求做出响应
如果将启动例程用c语言的方式表示(实际常用汇编编写):
exit(main(argc, argv));
3个退出函数
_exit 和 _Exit立即进入内核,exit则先执行一些清理函数,然后返回内核。main返回int值 和调用exit()是等价的。
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
atexit函数
atexit的参数是一个函数地址,当调用此函数时无需向它传递任何参数,也不期望返回一个值。exit调用函数的顺序与登记的顺序相反,多次登记会多次调用。
#include<sadlib.h>
int atexit(void (*func)(void));
环境表
每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个滋镇包含一个以null结束的c字符串的地址。而全局变量则包含了这个指针数组的地址:extern char **environ;
环境字符串是以 name=value的形式存储的,默认name是大写的字母组成。
c程序的存储空间布局
正文段:cpu执行的机器指令部分,通常是可共享的,只读。
初始化数据段:通常称数据段,包含明确地赋初值地变量。如:int i = 999;
为初始化数据段:通常称bbs段,程序开始执行前,内核将此段所有的数据初始化为0或空指针。如: long sss;
栈:自动变量以及每次函数调用时所需保存的信息都放在此段。c递归函数每次调用自身时,就用一个新的栈帧,一次函数调用实例中的变量集不会影响另一次函数调用实例中的变量。
堆:通常在堆中进行动态存储的分配,历史原因,堆位于为初始化数据段和栈之间。
上图程序的逻辑布局,32为Intel x86处理上的Linux,正文段从0x08048000单元开始,栈底则0xc0000000之下开始(栈从高地址向低地址方向增长),堆顶和栈顶之间有很大的虚地址空间。变成生成的a.out文件还包含了若干其他的段,如:符号表的段,调试信息的段以及动态共享库链接表的段等,这些部分并不装载到进程执行的程序映像中。
为初始化数据段的内容不存放在磁盘程序文件中,因为是内核在执行程序之前将其初始化为0,保存在磁盘程序文件中的段只有正文段和初始化数据段。
size 指令报告正文段,数据段和bss段的长度(以字节为单位)。如:
共享库
共享库其实就是动态库,使用动态库编译能极大减小应用程序的大小,而且在库发生改变时,只需要替换新库即可不需要再对整个程序编译(如果使用静态库就需要重新编译才可生效)。
编译时加上 -static 就是只使用静态库,不加的话默认支持共享库。
存储空间的分配
3个存储空间动态分配的函数:
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
1.malloc 分配制定字节数的存储空间,初始值不确定。
2.calloc 只制定数量制定长度的对象分配存储空间,分配的空间中每一位(bit)的值都是0
3.realloc 增加或减少以前分配去的长度。增加长度时,可能需要将之前分配区的数据一道另一个足够大的区域,以便在尾端提供增加的存储区,新增区域内初始值不确定。
3个分配函数返回的指针地址一定是适当对齐的,以便用于任何数据对象。超过分配的内存空间去使用的话,可能会覆盖前后其他分配对象的数据,导致数据损坏,而且这种问题不会很快暴露出来,很难发现。释放已经释放的块,调用free不是使用alloc函数返回的指针,忘记free造成内存泄漏(leakage)都会造成很严重的后果。
alloca函数提一下,功能与malloc函数一样,但是分配的空间是在栈中,当使用完后自动释放。但是这样就增加了栈帧的长度,某些系统在函数已被调用后不能增加栈帧长度。
环境变量
环境字符串的形式是:name=value
ISO C定义了一个函数getenv,获取环境变量值
#include<stdlib.h>
char *getenv(const char *name);//返回值:指向与name关联的value的指针,否则NULL
除了获取环境变量值,有时还需要改变环境变量或则增加环境变量值。当然也只是影响当前进程及其后生成和调用的子进程的环境,不影响父进程的环境,通常是指shell进程。
#include <stdlib.h>
int putenv(char *str);//成功返回0,否则返回非0
int setenv(const char *name, const char *value, int rewrite)://成功返回0,否则返回-1
int unsetenv(const char *name);//成功返回0,否则返回-1
1.putenv 取形式name=value的字符串,将其放到环境表中,如果name已存在,先删除原来的定义。
2.setenv 将name设置为value,如果name已存在,若rewrite非0,先删除原来定义,若rewrite为0,不删除也不报错。必须 分配存储空间,若将栈中的字符串作为参数传递给putenv就会发生错误,因为当函数返回时,栈帧的存储区可能被重用。
3.unsetenv 删除name的定义,不存在这种定义也不算出错。
因为环境变量表时存在程序的逻辑内存的顶部,而下面是栈,所以要对环境变量进行操作就比较麻烦,删变量还好,如果是增加或改变,可能就需要新申请堆上的空间了。
函数setjmp和longjmp
c中goto语句是不能跨越函数的,执行这种类型跳转功能的函数就是setjmp和longjmp。这用来处理深层嵌套函数调用中出错的情况很有用。
#include<setjmp.h>
int setjmp(jmp_buf env);//直接调用返回0,若从longjmp返回,则为非0
void longjmp(jmp_buf env, int val);
在希望返回到的位置调用setjmp。setjmp的参数类型是个特殊类型 jmp_buf。这是某种形式的数组,存放调用longjmp时的能用来恢复栈状态的所有信息。因为需要跨函数引用,env通常为全局变量。
全局变量、静态变量和易失变量不受编译优化影响,在longjmp之后,它们的值是最近所呈现的值。而auto和register变量因为保存在寄存器而回到setjmp时的状态。
函数getrlimit和setrlimit
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);//成功返回0,否则返回非0
int setrlimit(int resource, const struct rlimit *rlptr);//成功返回0,否则返回非0
struct rlimit{
rlim_t rlim_cur; //soft limit: current limit
rlim_t rlim_max;//hard limit:maximum values for rlim_cur
};
上面两个函数每次调用都指定一个资源以及一个指向上面结构的指针。每次更改资源:
1.任何进程都可将软限制值更改为小于或则等于硬限制的值。
2.任何进程都可降低硬限制值,但必须大于或等于软限制值,这种降低对普通用户是不可逆的。
3.只有超级用户进程可以提高硬限制值。
函数system
#include<stdlib.h>
int system(const char *cmdstring);
system中实现了fork,exec和waitpid,返回值有3种:
1.fork失败或则waitpid失败。返回除了EINTR之外的出错,system返回-1.并设置errno指示错误类型。
2.如果exec失败,返回值如同shell执行exit(127);
3.如果3个函数都成功,system返回shell的终止状态,格式在waitpid中说明。
不要通过system()来设置用户ID或则组ID,因为会被恶意程序用来调用其他恶意程序,存在安全问题。
用户标识
#include<unistd.h>
char *getlogin( void );//成功返回指向登录名字字符串的指针,出错返回NULL
进程调度
进程的调度是通过优先级的粗粒度来控制的。进程可以通过调整nice值降低优先级运行,只有特权进程允许提高调度权限。优先级越高对cpu越不友好,所以对应的nice是越低。
#include <unistd.h>
int nice(int incr);//成功返回新的nice值NZERO,出错返回-1
这个函数只能修改自己进程的nice值。incr参数会被直接加到进程的nice值上,如果太大会降到默认最大合法值 ,同理太小会被提升到最小合法值。
#include <sys/resource.h>
int getprority(int which, id_t who);//成功返回-NZER~NZERO-1之间的nice值,失败返回-1
int setpriority(int which, id_t who, int value);//成功返回0,失败返回-1
which取以下3个值:PRIO_PROCESS进程,PRIO_PGRP进程组,PRIO_USER用户ID。
进程时间
度量的时间有3个:墙上时钟时间,用户CPU时间和系统CPU时间。进程调用times函数可以获取自己以及已经终止的子进程的上述值。
#include<sys/times.h>
clock_t times(struct tms *buf);//成功返回流逝的墙上时钟,出错返回-1
struct tms{
clock_t tms_utime; //用户cpu时间
clock_t tmf_stime; //系统cpu时间
clock_t tms_cutime; //子进程用户cpu时间
clock_t tms_cstime; //子进程系统cpu时间
}
结构中没有墙上时间,而是通过返回值,但是这个值是某一时刻的度量值,不能使用绝对值,而是调用两次time,用这两个返回值的差作为墙上时钟时间。(长期运行的进程墙上时间可能会溢出)。
笔记的最后把书上的代码贴出来,可能会有些差异,里面包含的myerr.h头文件是为了解决调用err_sys()函数报错的问题,而这个头文件是放在apue.h目录下的,这个好像在前面的文章里有提过。其中有些函数是之前定义的,然后为了能运行,都放在一个文件里了,会有重复。
7-3
#include"apue.h"
#include "myerr.h"
static void my_exit1(void);
static void my_exit2(void);
int main(void)
{
if(atexit(my_exit2) != 0)
err_sys("can't register my_exit2");
if(atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
if(atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
printf("main is done\n");
return(0);
}
static void my_exit1(void)
{
printf("first exit handler\n");
}
static void my_exit2(void)
{
printf("second exit handler\n");
}
7-4
#include"apue.h"
#include "myerr.h"
int main(int argc , char *argv[])
{
int i;
for(int i = 0; i < argc; i++)
printf("argv[%d]:%s\n", i, argv[i]);
exit(0);
}
7-13
#include "apue.h"
#include "myerr.h"
#include <setjmp.h>
static void f1(int, int, int, int);
static void f2(void);
static jmp_buf jmpbuffer;
static int globval;
int main(void){
int autoval;
register int regival;
volatile int volaval;
static int statval;
globval=1; autoval=2;regival=3;volaval=4;statval=5;
if(setjmp(jmpbuffer) != 0){
printf("after longjmp:\n");
printf("globval=%d,autoval=%d,regival=%d,volaval=%d,statval=%d\n",
globval,autoval,regival,volaval,statval);
exit(0);
}
globval=95;autoval=96;regival=97;volaval=98;statval=99;
f1(autoval,regival,volaval,statval);
exit(0);
}
static void f1(int i, int j, int k, int l){
printf("in f1():\n");
printf("globval=%d, autoval=%d, regival=%d, volaval=%d, statval=%d\n", globval, i, j, k, l);
f2();
}
static void f2(void){
longjmp(jmpbuffer, 1);
}
~