目录
一、cpu/mmu
虚拟地址:可用空间有4G,MMU将虚拟地址映射到屋里内存中,即使运行的两个相同的程序,他们非内核区也是在屋里内存中不同的位置,但是,所有进程的内核空间在物理内存中只有一份拷贝
二、PCB
PCB是一个名为task_struct的结构体,它包含如下成员:
三、环境变量
1)字符串(本质)2)统一的格式:变量名=值(:值) 3)用来描述进程环境信息
使用形式:与命令行参数类似
加载位置:与命令行参数类似。位于用户区,高于stack
引入环境变量表:需要声明环境变量。extern char ** environ;
1 常见的环境变量
1.1 PATH
记录可执行程序的路径
1.2 SHELL
记录当前使用命令解析器
1.3 LANG
记录当前的语言
1.4 HOME
当前用户的home目录
2 getenv/setenv/unset函数
char * getenv(const char *name);
描述:
获取环境变量
返回值:
成功返回name对应的环境变量列表
失败返回NULL
int setenv(const char *name,const char *value,int overwrite);
描述:
如果name存在且override为0,那么value将不会更改name对应的值,否则,会覆盖原始环境变量的值
返回值:
成功返回0
失败返回-1,同时设置对应的errno
int unsetenv(const char *name);
描述:
删除环境变量name
返回值:
成功返回0
失败返回-1,同时设置对应的errno
代码示例:
#include <stdlib.h>//getenv/setenv/unsetenv
#include <stdio.h>
int main(int argc,char *argv[])
{
printf("getenv:%s\n",argv[1]);
char * value = getenv(argv[1]);
printf("%s=%s\n",argv[1],value);
printf("setenv:%s=%s\n",argv[1],argv[2]);
int ret = setenv(argv[1],argv[2],1);
printf("ret=%d\n",ret);
printf("unsetenv:%s=%s\n",argv[1],argv[2]);
ret = unsetenv(argv[1]);
printf("ret=%d\n",ret);
printf("getenv:%s\n",argv[1]);
value = getenv(argv[1]);
printf("%s=%s\n",argv[1],value);
}
四、进程管理
1 fork函数
pid_t fork(void);
描述:
创建一个子进程
返回值:
失败在父进程返回-1,并设置对应的errno
成功时有如下两种情况
1)在父进程中返回子进程pid(非负整数)
2)在子进程中返回0
代码示例
#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//peror
int main(int argc, char * argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
}
else
{
printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
}
return 0;
}
其中,getpid()为获取当前进程pid,getppid()为获取当前父进程pid;ps:父进程的父进程pid为当前的shell的pid。
父子进程相同的:
.data .text 堆 栈 环境变量 全局变量 宿主目录位置 进程工作目录 信号处理方式
父子不同:
进程id 返回值 各自的父进程 进程创建时间 闹钟 未决信号集
父子进程共享:
读共享,写独占
文件描述符 mmap映射区
特别的,fork之后父子进程执行的先后顺序不能确定
gdb调试
set follow-fork-mode parent
set follow-fork-mode child
2 exec函数族
int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , ..., char * const envp[]);
int execv( const char *path, char *const argv[]);
int execvp( const char *file, char *const argv[]);
描述:
exec族函数用来创建一个新的子进程,该进程会覆盖掉原来所有的数据。
返回:
只有失败的时候才会返回-1,并设置相应的errno,成功的时候直接执行新的子进程,因此,在程序异常处理的时候只需要在exec族函数下面加异常处理逻辑
代码示例:
#include <unistd.h>//exec族
#include <fcntl.h>//flags
#include <stdlib.h>//perror
#include <stdio.h>
int main(int argc,char * argv[])
{
int fd;
fd = open([argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd < 0)
{
perror("open error");
exit(1);
}
dup2(fd,STDOUT_FILENO);
execlp("ps","ps","ax",NULL);
perror("execlp error");
return 0;
}
exec函数族中的后缀含义:
l(list) 命令行参数列表
p(path)搜索file时使用path变量
v(vector)使用命令行参数数组
e(environment)使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
3 回收子进程
孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init,进程,称为init,进程领养孤儿进程。
代码示例:
#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//perror
int main(int argc, char * argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
sleep(3);
printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
}
else
{
printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
sleep(1);
}
return 0;
}
尝试在终端运行,结果如下图
我们发现,当父进程推出后,子进程的父进程pid已经由原来的4851变成了2219。我们继续在终端输入ps aux | grep 2219,发现该进程为/sbin/upstart。该进程为linux图形界面下一个专门回收孤儿进程的进程,它是init进程的子进程,如果在字符终端中,则只会由init进程回收子进程。
1
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。为了放大效果,我们将之前的代码修改为如下结果。父进程在死循环中打印输出,并且不回收子进程,而子进程打印十次之后就退出进程。
#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//peror
int main(int argc, char * argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
int i=0;
for(;i<10;i++)
{
printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
sleep(1);
}
}
else
{
while(1)
{
printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
sleep(1);
}
}
return 0;
}
我们在终端中运行该程序,待子进程退出之后,在另一个终端输入ps aux,结果如下。可以看到,最下有两个进程一个,进程id分别为6635(父进程),6636(子进程)。其中子进程使用"[]"包裹并用<defunct>,表示进程死亡但是未被回收(僵尸进程)。
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的而僵尸进程已经终止。
思考:用什么办法可清除掉僵尸进程呢?
wait/waitpid函数
#include <sys/wait.h>
pid_t wait(int * status);
pid_t waitpid(pid_t pid,int *status,int options);
参数:
1)pid
>0想要回收的pid
-1回收任意子进程
0回收当前调用waitpid的一个组
<-1回收进程组内的所有进程
ps:wait(NULL)与waitpid(-1,NULL,0)功能一样
2)status
传出参数,获取子进程的退出状态(return的返回值,信号的值)
ps:status可以使用多种宏定义来判断到底是哪种类型的退出状态
WIFEXITED(status)如果子进程正常结束则为非0 值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
3)option
设置waitpid是阻塞还是非阻塞,0表示阻塞,WNOHANG表示非阻塞示例代码:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
}
if (pid == 0) {
int i;
for (i = 3; i > 0; i--) {
printf("This is the child, pid=%u\n",getpid());
sleep(1);
}
exit(3);
}
else
{
int stat_val;
waitpid(pid, &stat_val, 0);
if (WIFEXITED(stat_val))
{
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
}
else if (WIFSIGNALED(stat_val))
{
printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
}
}
return 0;
}
运行该程序后,执行结果如下所示。由于子进程在返回时exit中传入3,因此通过WIFEXITED宏定义可以捕获到正常退出的状态,并且可以用WEXITSTATUS得到对应的值。
为了方便操作,我们将子进程的循环修改为死循环,再次运行该程序,我们在子进程退出之前,另起一个终端,发送sudo kill -9 11014,可以发现子进程因为信号而结束了进程,信号值为kill后的参数
内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、该进程使用的CPU时间总量。