一、进程环境
C程序存储空间布局:
正文段:只读、存放CPU执行的机器指令,可共享
数据段:也叫初始化数据段。包含程序中需要明确赋值的变量,如 int max =99;
BSS段:也叫未初始化的数据段,在程序开始执行前,内核将此段中数据初始化为0或空指针,如long sum[100];
栈:存放自动变量(即局部变量),以及每次函数调用时所需要保存的信息
堆:通常在堆中实现动态存储分配,位于BSS和栈之间
其中,如寄存器变量或全局变量存放在数据段或BSS段。图示如下:
二、动态库与静态库
1.静态库:链接阶段直接被链接到二进制文件中,生成文件体积大,但不再依赖于库。
2.动态库:运行阶段加载
1)C文件生成动态库 gcc -shared a.c -o libdb.so
2)调用方法:
运行时调用: 如调用libxxx.so库中的func()函数,则:gcc b.c -o bapp -l xxx.so
要求动态库加载路径默认在/lib和/usr/lib,或通过/etc/ld.so.conf配置或环境变量LD_LIBRART_PATH指定
手动加载动态库:利用C库中的dlopen等接口
打开库: *p = dlopen("libxxx.so",RTLD_NOW); //第二个参数可以更改
void (*func)(void) = dissym(p,"func");
func();
编译: gcc a.c -l xxx -o.app
三、进程控制
1.进程ID为0是交换进程,为1是init进程,获取pid调用函数pid_t getpid(void); 获取父进程pid用pid_t getppid(void);头文件都是<sys/types.h> 和 <unistd.h>,pstree命令可以查看进程家族树。
2.进程组:一次会话中的多个进程可以属于一个进程组(如管道的两个进程),进程组ID为PGID,会话ID为SID
pid_t getpgrp(void); int setpgid(pid_t pid,pid_t pgid);
对于一个已经执行exec的子进程,父进程不能调用setpgid来设置子进程的进程组ID
调用setsid(void)可以创建会话,但调用它的进程不能是某个进程组的组长。防止该进程组其他成员无法会话迁移。
3.进程的创建——fork()
调用folk创建进程,调用进程为父进程,返回进程为子进程。返回两次,一次是父进程的返回值返回子进程的进程号,另一次是子进程的返回值,返回0。子进程是父进程的副本,拷贝了一份父进程的堆栈空间,父子进程只共享正文段。
ret=fork();
if(ret==0) { //子进程代码段}
else if (ret>0) {//父进程代码段}
else { //fork 失败}
注意:一般在fork进程中,父子进程并不同步,因此不一定是父进程还是子进程先进行。子进程不继承父进程的文件锁,子进程未处理信号集设置为空集。
4.写时拷贝:子进程的页表项指向与父进程指向的物理内存页相同,当父子都只读时则公用一个物理内存页,但由一方尝试修改则会引发缺页异常,此时内核将为该页面创建一个新的物理页面并复制其内容,使得父子进程各自真正拥有自己的物理内存,然后将页表项标记位可写。
5.FD_CLOSEXEC(fd)与O_CLOSEXEC(open)标志位在子进程调用exec时会响应关闭文件,防止父子进程由于共享文件偏移量时读写文件发生混乱。
6.守护进程daemon
特点:在后台运行不受任何终端控制,一直到系统退出(如SIGINT、SIGQUIT、SITSTP等终端信号并不能干扰它,关闭终端也不能将其杀死。守护进程退出的情况只有stop命令、发送信号杀死守护进程或守护进程代码bug)。
创建方法:double_fork magic
1)fork()函数,父进程退出子进程继续
2)子进程摆脱环境控制:修改进程当前目录为根目录(chdir("/"))调用setsid切断与控制终端的关系并创建一个新的会话,设置文件模式创建掩码为0(umask(0))
3)再次fork(),父进程退出子进程继续,使得daemon不是会话首进程
4)关闭标准输入、标准输出、标准错误
也可以调用glibc库中的daemon函数一次创建:
#include <unistd.h>
int daemon(int nochdir,int noclose); //一般参数为0,0
7.进程的终止:正常——从main函数return、调用exit、调用_exit。异常——调用abort、接收到信号由信号终止
exit与_exit区别是,exit退出且调用清理函数,关闭打开的方法、写入缓冲数据、删除临时文件然后再调用_exit
8.等待子进程