快捷搜索:  汽车  科技

linux基础进程原来一点也不复杂(Linux进程基础知识)

linux基础进程原来一点也不复杂(Linux进程基础知识)MMU还可以修改程序访问内存级别,普通程序访问级别为0,操作系统为3,执行系统调用时,MMU修改了程序访问内存的访问级别,这个速度比较慢。在用户空间分配一个数组(虚拟内存空间中数组地址是连续的),如果数组长度很长,在映射到物理内存上时,其在物理内存上的地址其实是不连续的,但不影响,反正我们平常取得地址都是虚拟内存地址。虚拟内存与物理内存映射MMU:内存管理单元,完成虚拟地址到物理地址映射不同进程用户空间内存映射到不同物理内存区域,而内核空间内存映射到同一块物理内存区域,因为操作系统就一个,在这个物理内存区域包括了不同进程的PCB(结构体)。

程序:死的,只占用磁盘空间

进程:活的,运行起来的程序,占用系统资源(CPU,内存等)

单道程序设计:所有进程一个一个执行,A执行完了才能执行B

多道程序设计:进程相互穿插执行,并行执行

linux基础进程原来一点也不复杂(Linux进程基础知识)(1)

虚拟内存与物理内存映射

MMU:内存管理单元,完成虚拟地址到物理地址映射

linux基础进程原来一点也不复杂(Linux进程基础知识)(2)

linux基础进程原来一点也不复杂(Linux进程基础知识)(3)

不同进程用户空间内存映射到不同物理内存区域,而内核空间内存映射到同一块物理内存区域,因为操作系统就一个,在这个物理内存区域包括了不同进程的PCB(结构体)。

在用户空间分配一个数组(虚拟内存空间中数组地址是连续的),如果数组长度很长,在映射到物理内存上时,其在物理内存上的地址其实是不连续的,但不影响,反正我们平常取得地址都是虚拟内存地址。

MMU还可以修改程序访问内存级别,普通程序访问级别为0,操作系统为3,执行系统调用时,MMU修改了程序访问内存的访问级别,这个速度比较慢。

PCB进程控制块

linux基础进程原来一点也不复杂(Linux进程基础知识)(4)

进程状态

linux基础进程原来一点也不复杂(Linux进程基础知识)(5)

进程挂起的时候会释放CPU,例如sleep函数会挂起进程。

常用的几个环境变量

PATH:可执行文件搜索路径

SHELL:当前使用shell类型

TERM:当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

LANG:语言和字符编码方式

HOME:家目录路径

linux基础进程原来一点也不复杂(Linux进程基础知识)(6)

fork()

pid_t fork(void); pid_t getpid(void);//获取进程号 pid_t getppid(void);//获取父进程号 uid_t getuid(void);//returns the real user ID of the calling process. uid_t geteuid(void);//returns the effective user ID of the calling process. gid_t getgid(void);//returns the real group ID of the calling process. gid_t getegid(void);//returns the effective group ID of the calling process.

成功:父进程返回子进程进程号pid,子进程返回0;

失败:父进程返回-1,设置errno,不创建子进程;

linux基础进程原来一点也不复杂(Linux进程基础知识)(7)

子进程把父进程的数据复制一份

fork一般判断方式:

pid_t pid; pid = fork(); if(pid==-1) { perror("fork error"); } else if(!pid) { //... } else { //... }

循环创建多个子进程

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> /* int main(int argc char const *argv[]) { for(int i=0;i<5;i ) { if(fork()==0) { printf("--I am the %d child process.\n" i 1); break; } } return 0; } */ int main(int argc char const *argv[]) { int i = 0; for(i=0;i<5;i ) { if(fork()==0) { //printf("--I am the %d child process.\n" i 1); break; } sleep(1);//让后面打打印依序打印 } if(i==5) { printf("--I am the parent process.\n"); } else { printf("--I am the %d child process.\n" i 1); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(8)

父子进程共享哪些内容?

linux基础进程原来一点也不复杂(Linux进程基础知识)(9)

子进程把父进程的数据复制一份,父子进程间遵循读时共享写时复制原则。

fork之后先执行父进程还是子进程是不确定的,取决于内核的调度算法。

gdb调试

使用gdb调试时,gdb只能跟踪一个进程,可以在fork函数调用之前,通过指令设置gdb调试跟踪父进程还是子进程,默认跟踪父进程。

set follow-fork-mode child:设置gdb在fork之后跟踪子进程

se follow-fork-mode parent:设置gdb在fork之后跟踪父进程

注意:一定要在fork函数之前设置才有效。

linux基础进程原来一点也不复杂(Linux进程基础知识)(10)

不管跟踪父进程还是子进程,程序运行结果都是一样的。只是单步调试的时候,走的语句不一样。

exec函数族

fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数去执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序开始处开始执行,调用exec并不创建新进程,exec前后进程的id不变

将当前进程的.text,.data替换成要加载的程序的.text,.data。

int execl(const char *path const char *arg .../* (char *) NULL */); int execlp(const char *file const char *arg .../* (char *) NULL */); int execle(const char *path const char *arg .../* (char *) NULL char * const envp[] */); int execv(const char *path char *const argv[]); int execvp(const char *file char *const argv[]); int execvpe(const char *file char *const argv[] char *const envp[]); int execve(const char *filename char *const argv[] char *const envp[]);

l:表示参数以列表方式提供

v:表示参数以数组方式提供

p:表示在环境变量path路径下查找可执行文件。

e:使用环境变量数组,不实用进程原有的环境变量,设置新加载程序的环境变量

事实上,只有execve是真正的系统调用,其他的几个函数最终都是调用它,execve在man第二节,其他在第三节。

linux基础进程原来一点也不复杂(Linux进程基础知识)(11)

参数:

path:完整可执行程序路径 文件名

file:在环境变量path路径下查找可执行程序文件,只指定文件名即可

arg:传递给新进程的参数,必须以NULL结尾,其中arg[0]一般存程序名,

返回值:通常情况下,exec函数不会返回,调用成功,跳转到新的程序入口处;错误时,返回-1,并设置errno。

execlp:通常用来调用系统程序,如ls、data、cp、cat等。其实,execlp也会在当前目录下查找可执行程序。

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc char const *argv[]) { pid_t pid; pid = fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(!pid) { execlp("ls" "ls" "-l" "-a" "-h" NULL); //execlp("ls" "ls" "-alh" NULL);//这里这样写效果一样 perror("execlp error");//execlp出错才返回执行 exit(1); } else { sleep(1); printf("--parent process---\n"); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(12)

execl:一般用于执行自己的程序

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc char const *argv[]) { pid_t pid; pid = fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(!pid) { execl("./printf_hello" "./printf_hello" NULL);//main函数命令行参数 //execlp("./printf_hello" "./printf_hello" NULL);//这里也可以execlp 它也会查找当前路径 perror("execl error");//execl出错才返回执行 exit(1); } else { sleep(1); printf("--parent process---\n"); } return 0; } #include <stdio.h> int main(int argc char const *argv[]) { printf("hello world\n"); return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(13)

execvp:

char *arg[] = {"ls" "-a" "-l" "-h" NULL};

execvp("ls" arg);

孤儿进程:

父进程先于子进程结束,子进程变为孤儿进程,systemd进程会成为孤儿进程的父进程

ps ajx

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc char const *argv[]) { pid_t pid; pid = fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(!pid) { while(1) { printf("--child process--pid=%d--ppid=%d--\n" getpid() getppid()); sleep(1); } } else { printf("--parent processs--pid=%d--\n" getpid()); sleep(9); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(14)

linux基础进程原来一点也不复杂(Linux进程基础知识)(15)

linux基础进程原来一点也不复杂(Linux进程基础知识)(16)

kill -9 杀掉子进程

僵尸进程:

进程终止,父进程尚未回收。子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

僵尸进程是不能用kill命令清除掉的,因为kill命令是用来终止进程的,而僵尸进程已经终止,可以kill掉僵尸进程父进程,来让系统进行回收。

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc char const *argv[]) { pid_t pid; pid = fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(!pid) { printf("--child process--pid=%d--ppid=%d--\n" getpid() getppid()); sleep(9); } else { while(1) { printf("--parent processs--pid=%d--\n" getpid()); sleep(1); } } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(17)

子进程结束前:

linux基础进程原来一点也不复杂(Linux进程基础知识)(18)

子进程结束后:

linux基础进程原来一点也不复杂(Linux进程基础知识)(19)

defunct:死者

wait函数

阻塞回收任意一个子进程

一个进程在终止时会关闭掉所有的文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息;如果是正常终止,则保存着退出状态,如果是异常终止,则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,是因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。

父进程调用wait回收子进程终止信息,wait函数包括三个功能:

1. 阻塞等待子进程退出

2. 回收子进程残留资源

3. 获取子进程结束状态(退出原因,正常退出->退出值,异常终止->终止信号)

pid_t wait(int *stat_loc);

stat_loc:传出参数,保存子进程的退出状态

成功:返回回收的子进程的pid,

失败:返回-1

判断进程退出状态的几个宏:

WIFEXITED(stat_val)//判断进程是否正常终止 WEXITSTATUS(stat_val)//取退出值 WIFSIGNALED(stat_val)//判断进行是否被信号终止 WTERMSIG(stat_val)//取信号值 WIFSTOPPED(stat_val) WSTOPSIG(stat_val) WIFCONTINUED(stat_val)

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/wait.h> int main(int argc char const *argv[]) { pid_t pid; pid_t wpid; int status; pid = fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(!pid) { printf("--child process--pid=%d--ppid=%d--\n" getpid() getppid()); sleep(10); return 100;//查看退出状态是否为100 } else { int wpid = wait(&status); if(wpid==-1) { perror("wait error"); exit(1); } printf("--wpid=%d----\n" wpid); if(WIFEXITED(status))//为真 说明子进程正常终止 { printf("--child process exit with %d\n--" WEXITSTATUS(status)); } if(WIFSIGNALED(status))//为真 说明子进程是被信号终止 { printf("--child process killed with %d\n--" WTERMSIG(status)); } } return 0; }

正常退出:

linux基础进程原来一点也不复杂(Linux进程基础知识)(20)

使用信号杀死:

linux基础进程原来一点也不复杂(Linux进程基础知识)(21)

linux基础进程原来一点也不复杂(Linux进程基础知识)(22)

当父进程不关心子进程的退出状态时,wpid=wait(NULL);

waitpid函数

pid_t waitpid(pid_t pid int *stat_loc int options);

返回值:

>0:表示成功回收的子进程pid

0:函数调用时,options设置为WNOHANG,并且,没有子进程结束。WNOHANG--wait no hang。hang:挂起

-1:失败,设置errno。

参数:

options:设置为WNOHANG时,指定回收方式为非阻塞。默认为0,阻塞。

WNOHANG return immediately if no child has exited.

pid:

>0:回收指定id的子进程

-1:回收任意子进程(相当于wait)

0:回收和当前调用watipid在一个组的所有子进程

<-1:回收指定进程组内的任意子进程

stat_loc:子进程退出状态,和wait函数中参数含义一样。

waitpid(-1 NULL 0);等价于wait(NULL);

waitpid示例:

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/wait.h> int main(int argc char const *argv[]) { int i = 0; int pid pid_waitfor wpid; for(i=0;i<5;i ) { pid = fork(); if(!pid) { break; } if(i==2) { pid_waitfor = pid;//指定回收第三个子进程,父进程中保存第三个子进程pid } //sleep(1); } if(i==5) { wpid = waitpid(pid_waitfor NULL 0);//阻塞等待回收第三个子进程 //wpid = waitpid(-1 NULL WNOHANG);//不阻塞回收任意一个子进程,没有结束的子进程,返回值为0。 //wpid = waitpid(-1 NULL 0);//阻塞回收任意一个子进程。 if(wpid==-1) { perror("wait error"); exit(1); } printf("--parent process--waitfor %d--\n" wpid); } else { sleep(1); printf("--child process--pid=%d--\n" getpid()); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(23)

wait、waitpid函数一次只能回收一个子进程,要回收多个子进行需要循环。

循环回收多个子进程

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/wait.h> int main(int argc char const *argv[]) { int i = 0; int pid wpid; for(i=0;i<5;i ) { pid = fork(); if(!pid) { break; } } if(i==5) { /* while((wpid=waitpid(-1 NULL 0))!=-1)//注意最后返回-1 { printf("--wait child--%d\n" wpid); sleep(1); } */ while((wpid=waitpid(-1 NULL WNOHANG))!=-1) { if(wpid>0) { printf("--wait child--%d\n" wpid); } else if(wpid==0) { sleep(1); } } } else { sleep(1); printf("--child process--pid=%d--\n" getpid()); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(24)

发现waitpid没有子进程可回收后返回-1。

#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/wait.h> int main(int argc char const *argv[]) { int wpid; //wpid=waitpid(-1 NULL 0); wpid=waitpid(-1 NULL WNOHANG); //两种结果一样,不管有没有设置阻塞,没有子进程可回收后返回-1 if(wpid==-1) { printf("没有子进程,返回-1\n"); } if(wpid==0) { printf("没有子进程,返回0\n"); } return 0; }

linux基础进程原来一点也不复杂(Linux进程基础知识)(25)

猜您喜欢: