进程管理
进程运行后会进入执行状态:活跃active,活着alive,运行程序running program
进程IDprocess ID->pid;
进程若产生一个新的进程,则它称为父进程,新的进程成为子进程。
进程ID会表示为 pid_t
类型
问题:
子进程创建成功之后,代码的执行位置?
父进程执行到了哪儿,子进程就从哪开始执行
父子进程的执行顺序?
不一定,谁抢到cpu
谁执行
如何区分父子进程?
通过fork函数的返回值
fork
创建子进程
fork的返回值
>0:父进程的返回值
=0子进程的返回值
getpid/getppid
getpid
得到子进程的pid
getppid
得到父进程的pid
子进程运行时候,会从父进程执行到的位置开始执行,运行结果如下:
可以得到,得到的子进程的代码与父进程完全一致;
若要循环得到子进程,可以增加一个判断
for (int i = 0; i < 4; i++) {
pid = fork();
if(pid==0){
break;
}
}
则子进程不会再走fork
操作了。
fork出来后,两个地址空间的用户区数据完全相同,但是后续进行了各自的操作,各个进程的地址空间中的数据是完全独立的。
共享全局变量,也就是说,全局变量在每个子进程与父进程中全局变量的改变,不会影响其他进程的该变量的值。
读时共享,写时复制。
创建兄弟进程
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = -1;
int i = 0;
for (i = 0; i < 3; ++i) {
pid = fork();
if (pid == 0) //如果当前进程是子进程,就跳出此循环,不在进行 fork() 创建新的子进程
break;
}
if (i == 0)
printf("this is a child process (i == 0), pid = %d ppid = %d\n", getpid(), getppid());
if (i == 1)
printf("this is a child process (i == 1), pid = %d ppid = %d\n", getpid(), getppid());
if (i == 2)
printf("this is a child process (i == 2), pid = %d ppid = %d\n", getpid(), getppid());
if (i == 3)
printf("this is a parent process (i == 3), pid = %d ppid = %d\n", getpid(), getppid());
return 0;
}
ps
用于查看系统中的进程状态,格式为“ps
[参数]”。
ps a
基础信息
ps au
ps aux
查看所有进程状态
可以用 ps aux | grep"xxx"
或ps ajx | grep"xxx"
查看更详细的信息;
kill
杀死指定进程
kill -9 + 进程pid
exec函数族
让父子进程执行不相干的函数;
能够替换进程空间中的源代码段;
当前程序中调用另一个应用程序:要首先想到使用exec前需要fork
返回值:如果函数执行成功,不返回;失败则需要打出错误信息,退出子进程;
perror("execlp");
exit(1);
execl
启动另一个进程,常为自己写的程序;
执行指定目录下的程序.
int execl(const char *path, const char *arg, ..);
path
:要执行的程序的绝对路径
变参arg
:要执行的程序的需要的参数
第一arg:占位
后边的arg:命令的参数,该参数可以有很多
参数写完之后:NULL;标识该参数结束
一般执行自己写的程序
下面程序来操作ls命令
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
int main(int argc, const char* argv[]) {
//创建一个子进程
pid_t pid;
//父进程数
for (int i = 0; i < 8; i++) {
printf("parent i = %d\n", i);
}
pid = fork();
//让子进程执行ls命令
if (pid == 0) {
//ls用的是子进程的地址空间
execl("/bin/ls", "666", "-lah", NULL);//NULL指编译结束
}
for (int i = 0; i < 3; i++) {
printf("child i = %d\n", i);
}
}
让父进程与子进程做不一样的操作
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
int main(int argc, const char* argv[]) {
//创建一个子进程
pid_t pid;
//父进程数
for (int i = 0; i < 8; i++) {
printf("parent i = %d\n", i);
}
pid = fork();
//让子进程执行
if (pid == 0) {
execl("/home/xiaoma/Xiaoma/process/hello", "hello", NULL);//NULL指编译结束
}
for (int i = 0; i < 3; i++) {
printf("child i = %d\n", i);
}
}
execlp
调用系统的命令;
执行PATH环境变量能够搜索到的程序
int execlp(const char *file, const char *arg, ..);
file:执行的命令的名字
■第一arg:占位
■后边的arg:命令的参数
■参数写完之后:NULL
■执行系统自带的程序
口/bin
execlp执行自定义的程序: file参数绝对路径
。int execvp(const char *file, char *const argv[]);
■参数:
2、进程回收
概念:
孤儿进程:
父进程产生子进程,父进程结束,子进程依旧运行,子进程由init
进行领养,也就是成为了孤儿。
为了释放子进程占用的系统资源,进程结束后,能够释放用户区空间,因为子进程释放不了PCB,需要父进程进行释放;
僵尸进程
子进程结束,父进程仍在运行,但父进程不去释放子进程的pcb,则子进程成为僵尸进程;
父进程此时不能称为一个进程
进程回收
wait 阻塞函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait
或waitpid
得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *status);
返回值:
-1:回收失败,无子进程
>0:回收是子进程对应的pid
参数: status
判断子进程是如何死的
正常退出
被某个信号杀死了
调用一次只能回收一个子进程
status–传出参数
获取退出时候的返回值(正常退出)
``return 0; – mainexit(0);
WIFEXITED(status) > 0
WEXITSTATUS(status)
被信号杀死
WIFSIGNALED(status)> 0`
WTERMSIG(status)
waitpid
作用同wait,但可指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int \*status, in options);
成功:返回清理掉的子进程ID;
失败:-1(无子进程)
特殊参数和返回情况:
参数pid:
> 0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
循环回收
while((wpid=waitpid(-1,&status,xx)!=-1));
==0 回收当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行。
options
0-waitpid阻塞
WNOHANG-非阻塞
返回值:
-1:回收失败,无子进程
返回值>0:被回收的子进程
如果为非阻塞:
=0,子进程处于运行状态
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
作业
1.编写测试程序,测试父子进程之间是否共享文件。
共享文件-文件描述符
文件描述符-》pcb
打开一个文件- fd
fork();
父进程-write
子进程- read
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main(int argc, const char* argv[]) {
int fd = open("temp", O_CREAT | O_RDWR, 0664);
if (fd == 1) {
perror("open");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
}
if (pid > 0) {
char* p = "你猜我想干啥?";
write(fd, p, strlen(p) + 1);
close(fd);
}
else if(pid==0){
//睡1s保证父进程已经完成了文件的写操作
sleep(1);
char buf[1024];
lseek(fd, 0, SEEK_SET);
int len = read(fd, buf, sizeof(buf));//读取输入
printf("%s\n", buf);
close(fd);
}
return 0;
}
2.父进程fork三个子进程:
其中一个调用ps命令;
一个调用自定义应用程序;
一个调用会出现段错误的程序。
父进程回收三个子进程(waitpid
),并且打印三个子进程的
退出状态。
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(int argc, const char* argv[]) {
int num = 3;
int i = 0;
pid_t pid;
//需要三个子进程
for (i = 0; i < num; i++) {
pid = fork();
if (pid == 0) {
break;
}
}
if (i == 0) {
execlp("ls", "ls", NULL);
perror("execlp ps");
exit(1);
}
else if (i == 1) {
execl("/home/xiaoma/Xiaoma/process/hello", "hello", NULL);
perror("execl hello");
exit(1);
}
else if (i == 2) {
execl("./hello", "hello", NULL);
perror("execl error");
exit(1);
}
else if (i == num) {
//回收
int status;
pid_t wpid;
while ((wpid = waitpid(-1,&status,WNOHANG)) != -1) {
if(wpid==0){
continue;
}
printf("child died pid= %d\n", wpid);
if (WIFEXITED(status)) {
printf("return value %d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
printf("died by signal: %d\n", WTERMSIG(status));
}
}
}
}