进程与线程
1、进程的相关概念
(1)、进程与程序:
- 进程是动态的,程序是静态的;
- 进程有生命周期,程序没有生命周期;
- 一个进程只能对应一个程序,一个程序却可以对应多个进程,没有建立进程的程序不能作为一个独立的单位获得操作系统的认可;
(2)、进程控制块(PCB)
每个进程在内核中都有一个进程控制块来维护进程的相关信息,linux内核的进程控制块是task-struct结构体。内部成员很多,主要有:
- 进程ID:系统中每个进程都有唯一的id,用pid_t类型表示。
- 进程状态:有初始、就绪、运行、挂起、终止五个状态。
- 进程切换需要保存和恢复的CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录。
- 文件描述符表:包含很多指向file结构体的指针。
- 和信号相关的信息。
- 用户组id和组id。
- 会话(session)和进程组。
- umask掩码。
- 进程可以使用的资源上限。
2、进程控制
(1)、fork函数
pid_t fork(void)
问题:
-
fork函数的返回值?
答:当fork函数创建子进程成功后,会返回两个,一个数为0:代表子进程的返回值;当返回值大于0时:父进程返回值,代表子进程的id。 -
子进程创建成功后,代码的执行位置?
答:父进程执行到哪,子进程就从哪里开始执行。 -
父子进程的执行顺序?
答:随机执行,谁先抢到cpu,谁先执行。 -
如何区分父子进程?
答:通过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;
- 一般执行自己写的程序;
#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,函数阻塞;
0:参数3为WNOHANG,且子进程正在运行;
- pid:
- 参数:
- pid:
- pid==-1:等待任一个子进程,与wait函数等效;
- pid>0:等待其的进程ID与pid相等的子进程;
- pid==0:等待其组ID等于调用进程的组ID的任一子进程;
- pid<-1:等待其组ID等于pid的绝对值的任一子进程;
- status:子进程的退出状态,用法同wait函数;
- options:设置为WNOHANG,函数为非阻塞,设置为0,函数阻塞;
- pid:
- 调用一次wait函数,只能回收一个子进程。可以选择回收那个子进程;
- 头文件: