CSAPP-进程控制

获取进程ID

每个进程都有一个唯一的正数进程ID,getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)

创建和终止进程

1.运行

2.停止

3.终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{

pid_t pid;
int x= 1;

pid = Fork();
if(pid==0){
printf("child : x=%d\n",++x);
exit(0);
}

printf("parent: x=%d\n",--x);
}

要点:

1.fork函数只调用一次,但是会返回两次,一次是在父进程中,另外一次是在子进程中。

2.父进程总会返回子进程的PID,在子进程当中,fork总会返回0

3.并发执行,内核能够一任意方式交替执行它们逻辑控制流中的指令
在x86上,先完成父进程,再完成子进程,这个顺序不同的系统会不同,画出进程图,所有拓扑排序序列都可以。

4.相同但是独立的地址空间,每个进程都有相同的用户栈,相同的本地变量值,堆等“环境”,但却是独立的,就是说一个进程改变内部的环境不会影响另外一个进程

5.共享文件共享文件

结果如下
filename  exists, renamed

所以上面程序的分析:

pid==0,即子进程

那如果是以下程序呢?

upload succful

filename ready exists, renamed

解析:

1
2
3

先调用父进程,x=1,所以输出--x=0;
然后调用子进程,子进程除了会执行if内的printf语句,同时还会执行if外的printf语句。

回收子进程

当一个进程由于某种原因中止的时候,内核不会立刻把它从系统中清除,而是保存在一个已经终止的状态中,等待被父进程回收。

所以一个终止了的但是还没有被回收的进程,成为僵死进程

如果父进程终止了,内核会安排init进程成为它孤儿进程的养父,init进程的PID位1,是系统启动的时候由内核创建的,不会终止,是所有进程的祖先。

1
2
pid_t waitpid(pid_t pid,int *statsup,int options){
}

pid参数用来判断等待集合的成员。

如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid

如果pid=-1,那么等待集合是由父进程的所有子进程组成的

wait函数

wait函数是waitpid函数的简单版本

1
pid_t wait(int *statusp); // = waitpid(-1,&status,0);

让进程休眠

//返回要休眠的秒数

1
unsigned int sleep(unsigned int secs);

1
2
3
4
int pause (void)

让函数休眠,知道进程收到一个信号
//always return -1

加载并运行程序

execve函数在当前进程上下文当中加载并且运行一个新程序

1
int execve(const char *filename,const char *argv[],const char *envp[])

execve函数加载并且运行可执行目标文件,且带参数列表argv和环境变量envp,只有当出现错误例如找不到filename的时候,execve才会返回到调用程序。

execve函数调用一次,而且从不返回

当execve加载了filename后,启动代码设置栈,并将控制传递给新程序的主函数。

1
int main(int argc,char *argv,char **envp)

1.argc argv[]数组中非空指针的数量

2.argv argb[]数组中的第一个条目

3.envp 指向argv[]数组的第一个条目

1
2
3
char *getenv(const char *name) 函数

搜索字符串 name==value,找到就返回一个指向其的指针,否则返回NULL;
1
2
3
int setenv(const char *name,const char *newvalue,int overwrite)//当overwrite非0的时候,如果name不存在,那么setenv把“name=newvalue"添加到数组当中

unsetenv就是会删除它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//查看函数的命令行参数和环境变量

int main(int argc,char *argv[],char *envp[])
{
int i;
printf("command-line arguments:\n");
for(i=0;argv[i]!=NULL;i++){

printf(" argv[%2d]: %s\n",i,argv[i]);
}
printf("\n");
printf("environmental variables:\n");
for(i=0;envp[i]!=NULL;i++){

printf(" envp[%2d]: %s\n",i,envp[i]);
}
exit(0);

}

用户栈的组织结构

动态链接器变量下的envp,argv,与argc是libc_start_main的栈帧

upload succful

信号

def:一条通知进程系统发生了一个某种类型事件的小信息

传送一个信号到目的进程由两个不同步骤组成:

发送信号

进程组:

每个进程属于一个进程组

1
pid_t getpgrp(void); return id of id group
1
int setpgid(pid_t pid,pit_t pgid);
用/bin/kill发送信号
1
2
3
/bin/kill -9 15213

/bin/kill -9 -15213 负的PID会导致信号被发送到进程组PID中的每个进程
从键盘发射信号

作业(job)用来表示为一条命令行求值而创建的进程,在任何时候,至多只有一个前台作业和0个或多个后台作业。

1
ls | sort //会创建一个由两个进程组成的前台作业
用kill发射信号
1
2
3
4
int kill (pid_t pid,int sig);
成功则返回0,错误则返回-1

如果pid大于0,则kill函数发送信号号码sig给进程pid,如果pid为0,则kill发送信号sig给调用进程所在进程组的每个进程,如果pid小于0,kill发送信号sig给进程组pig绝对值中的每个进程

接收信号

1.内核位每个进程在pending位向量维护着待处理信号的集合,而在blocked位向量维护着被阻塞的信号集合。所以任何时刻一种类型的信号只会被接收一次,在处理它的时候,会先把该类型的信号block,进程可以忽略信号,也可以捕捉这个信号,执行信号处理程序。

2.当内核从一个异常处理程序返回的时候,准备吧控制传递给某个进程p的时候,会检查进程p违背阻塞的待处理信号集合。如果这个集合不为空,那么内核选择集合中的某个信号k(越小越好,因为linux里面编号越小,优先级越高),并且进入k的处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 改变blocked向量的值,若oldset!=null,会用来保存以前blocked向量的值 */ 

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

/* 初始化set为空集 */ int sigemptyset(sigset_t *set); /* 初始化set全为1,每个信号都填入blocked向量 */

int sigfillset(sigset *set);

/* 添加、删除signum到set */
int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum);

/* set中对应signum是否置1 */

int sigismember(const sigset_t *set, int signum);
---------------------

原文:https://blog.csdn.net/WMLWONDER/article/details/53728630

singal函数

1
sighandler_t signal(int signum,sighandler_t handler)

如果handler不是SIG_IGN或者SIG_DFL,handler 就是用户定义的函数的地址,这个函数叫做信号处理程序。这个过程叫设置信号处理程序,调用过程叫做捕获信号,执行信号处理过程叫做处理信号

阻塞和解除阻塞信号

隐式阻塞:例如信号s对应程序S,当在处理程序S的时候,如果发送进程一个信号s,那么直到S返回,s会一直是待处理而不被接收。

显式阻塞:使用sigprocmask函数和其辅助函数,明确阻塞和解除阻塞选定的信号。

临时阻塞一个信号
1
2
3
4
5
6
7
8
9
10
11
12

sigset_t mask,prev_mask;
Sigemptyset(&mask);
Sigaddset(&mask,SIGINT);

//block sigint and save previous blocked set

Sigprocmask(SIG_BLOCK,&mask,&prev_mask);

//Code region that will not be interrupt by SIGINT

Sigprocmask(SIG_SETMASK,&prev_mask,NULL);
非本地跳转

filename aready exists, renamed

一些原则:

1.注意保存与恢复errno

2.当访问一个全局数据结构的时候,阻塞所有的信号

3.用volatile声明全局变量,告诉编译器不要缓存这个变量,那么每次引用g的时候,都要从内存中读取g的数值。