进程与线程
1、进程的相关概念
(1)、进程与程序:
1、进程是动态的,程序是静态的;
2、进程有生命周期,程序没有生命周期;
3、一个进程只能对应一个程序,一个程序却可以对应多个进程,没有建立进程的程序不能作为一个独立的单位获得操作系统的认可;
(2)、进程控制块(PCB)
每个进程在内核中都有一个进程控制块来维护进程的相关信息,linux内核的进程控制块是task-struct结构体。内部成员很多,主要有:
a、进程ID:系统中每个进程都有唯一的id,用pid_t类型表示。
b、进程状态:有初始、就绪、运行、挂起、终止五个状态。
c、进程切换需要保存和恢复的CPU寄存器。
d、描述虚拟地址空间的信息。
e、描述控制终端的信息。
f、当前工作目录。
g、文件描述符表:包含很多指向file结构体的指针。
h、和信号相关的信息。
i、用户组id和组id。
j、会话(session)和进程组。
k、umask掩码。
l、进程可以使用的资源上限。
2、进程控制
(1)、fork函数
pid_t fork(void)
问题:
a、fork函数的返回值?
答:当fork函数创建子进程成功后,会返回两个,一个数为0:代表子进程的返回值;当返回值大于0时:父进程返回值,代表子进程的id。
b、子进程创建成功后,代码的执行位置?
答:父进程执行到哪,子进程就从哪里开始执行。
c、父子进程的执行顺序?
答:随机执行,谁先抢到cpu,谁先执行。
d、如何区分父子进程?
答:通过fork函数的返回值
// 通过判断for循环的打印结果即可知道问题b与问题c答案 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,const char* argv[]) { pid_t pid; for(int i=0;i<4;i++) { printf("----i-----=%d\n",i); } pid = fork(); //父进程 if(pid>0) { printf("parent process,pid=%d"\n",getpid()); } // 子进程 else if(pid == 0) { printf("child process,pid=%d,ppid=%d\n"\n",getpid(),getppid()); } for(int i=0;i<4;i++) { printf("i=%d\n",i); } return 0; }
(2)、循环创建多个子进程
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,const char* argv[]) { int number=3;//要创建的子进程个数 pid_t pid; for(int i=0;i<number;i++) { pid = fork(); if(pid==0)//子进程不再进程fork() { break; } } //如何判断是第几个孩子 if(i==0) { printf("first process,pid=%d\n",getpid()); } if(i==1) { printf("second process,pid=%d\n",getpid()); } if(i==2) { printf("third process,pid=%d\n",getpid()); } if(i==number) { printf("father process,pid=%d\n",getpid()); } return 0; }
2、进程相关命令
1、查询某个进程的id
ps aux | grep XXX ps ajx | grep XXX
2、kill 向指定的进程发送信号
查看信号:
kill -l
杀死某个进程:
kill -9(SIGKILL) pid
3、进程之间的数据共享
父子进程之间的数据:
读时共享,写时复制。
4、exec函数蔟
- (1)、exec函数:
- 让父子进程执行不相干的操作;
- 能够替换进程地址空间中的源代码.txt段;
- 在当前程序中调用另外一个程序;
- 在执行exec之前需要fork();
- 返回值:
- 如果函数执行成功,不返回;
- 如果执行失败,打印错误信息,退出当前进程;
- (2)、执行指定目录下的程序:
int execl(cnst char* path,const char* arg,...)
- path:要执行的程序的绝对路径;
- 变参arg:要执行的程序所需要的参数;
- 第一arg:占位;
- 后边的arg:命令的参数;
- 参数写完以后:NULL;
一般执行自己写的程序;
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,const char* argv[])
{pid_t pid = fork();
for(int i=0;i<4;i++)
{
printf("it is parent process\n");
}
//子进程执行ls命令
if(pid==0)
{
execl("/bin/ls","ls","-l",NULL);
}
//运行程序后,会发现下面的代码只有父进程打印了
for(int i=0;i<4;i++)
{
printf("=====it is parent process====\n");
}
return 0;
}
* **(3)、执行PATH环境变量能够搜索到的程序:**
int execlp(cnst char file,const char arg,...)```
* file:要执行的命令的位置
* 变参arg:要执行的程序所需要的参数
* 第一arg:占位
* 后边的arg:命令的参数
* 参数写完以后:NULL
* 执行系统自带的程序
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,const char* argv[]) { pid_t pid = fork(); for(int i=0;i<4;i++) { printf("it is parent process\n"); } //子进程执行ps命令 if(pid==0) { //这里不需要指定路径,系统会在PATH中找到该命令 int ret = execlp("ps","ps","aux",NULL);. perror("Execlp:");//如果execlp执行错误(返回-1),系统才会执行该代码。(所以可以不写ret) exit(1); } //运行程序后,会发现下面的代码只有父进程打印了 for(int i=0;i<4;i++) { printf("=====it is parent process====\n"); } return 0; }
5、进程回收
1. 孤儿进程
- 爹生孩子;
- 爹先死,孩子还活着,孩子叫孤儿进程;
- 孤儿被init进程领养,init进程变为孤儿进程的父亲;
- 为了释放子进程占用的系统资源;
- 进程结束后,能够释放用户去空间;
- 释放不了pcb,必须由父进程释放;
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,const char* argv[]) { pid_t pid = fork(); if(pid==0) { sleep(1);//子进程睡一秒,保证父进程先死 printf("child pid = %d,ppid = %d\n",getpid(),getppid()); } else if(pid > 0) { printf("==========parent process=========="); printf("parent pid = %d,ppid = %d\n",getpid(),getppid()); } // 可以看到,程序运行完以后,两个parent pid不一样,因为父亲先挂,子进程被干爹init进程收养 return 0; }
2. 僵尸进程 - 孩子死了,爹还活着,爹不去释放子进程的pcb,孩子变为僵尸进程;
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,const char* argv[]) { pid_t pid = fork(); if(pid==0) { printf("child pid = %d,ppid = %d\n",getpid(),getppid()); } else if(pid > 0) { //父进程死循环,子进程挂掉后,父进程没办法去回收子进程pcb,孩子变为僵尸进程 while(1) { printf("==========parent process=========="); printf("parent pid = %d,ppid = %d\n",getpid(),getppid()); } } // 假设程序名字为app,则执行完以后 去查 ps aux | grep app,可以看到有个app的状态为Z+(zombie:僵尸) return 0; }
** 3. 进程回收**
编程过程中,有时需要让一个进程等待另一个进程,最常见的是父进程等待自己的子进程,或者父进程回收自己的子进程资源包括僵尸进程。
- (1)、wait(阻塞函数)
pid_t wait(int *status);
- 头文件:
- #include <sys/types.h> /* 提供类型pid_t的定义*/
include <wait.h>
- 函数作用:
- 阻塞并等待子进程退出;
- 回收子进程残留资源;
- 回去子进程结束的原因;
- 返回值:
- -1:回收失败:已经没有子进程了;
- >0:回收的子进程对应的的pid;
- 参数:status,子进程的退出状态
- 判断子进程是如何挂掉的(需要四个宏)
- WIFEXITED(status):是否正常退出,如果该宏为真,则使用下面这个宏可以获取进程退出的状态(exit/return)的参数;
- WEXITSTATUS(status);
- WIFSIGNALED(status):是否被某个信号杀死,如果该宏结果为非零,则使用下面的宏可以取得使进程终结的那个信号的编号
- WTERMSIG(status);
- 调用一次wait函数,只能回收一个子进程;
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <wait.h> int main(int argc,const char* argv[]) { pid_t pid = fork(); if(pid >0)//父进程回收子进程 { printf("parent process,pid = %d,ppid = %d\n",getpid(),getpid()); int status; pit_t wpid = wait(&status); //判断是否是正常退出的 if(WIFEXITED(status)) { // 打印退出状态参数 printf("exit value:%d\n",WEXITSTATUS(status)); } //判断是否是被信号杀死的 if(WIFEXITED(status)) { // 查看是被那个信号杀死的 printf("killed by signal:%d\n",WTEMSIG(status)); } printf("died child pid = %d\n",wpid); } else if { sleep(2); printf("child process,pid = %d,ppid = %d\n",getpid(),getpid()); } return 0; }
- 头文件:
- (2)、 waitpid
pid_t waitpid(pid_t pid,int *status,int options);
- 头文件:
- #include <sys/types.h> /* 提供类型pid_t的定义*/
include <wait.h>
- 函数作用:
- 同wait函数;
- 返回值:
- -1:回收失败:已经没有子进程了;
- >0:回收的子进程对应的的pid;
- =0:参数3为WNOHANG,且子进程正在运行;
- 参数:
- pid:
- pid==-1:等待任一个子进程,与wait函数等效;
- pid>0:等待其的进程ID与pid相等的子进程;
- pid==0:等待其组ID等于调用进程的组ID的任一子进程;
- pid<-1:等待其组ID等于pid的绝对值的任一子进程;
- status:子进程的退出状态,用法同wait函数;
- options:设置为WNOHANG,函数为非阻塞,设置为0,函数阻塞;
- pid:
- 调用一次wait函数,只能回收一个子进程。可以选择回收那个子进程;
- 头文件: