进程基础知识
一. 进程和程序
程序:死的。只占用磁盘空间。
进程:活的。运行起来的程序。占用内存、cpu等系统资源。
并发和并行:**并行是宏观上,**指两个或多个事件在同一个时间段内的概念。 **并发是微观上串行,**不是一个时间点,而是一个时间段内的概念
二. PCB控制块
每个进程在内核中都有一个进程控制块(pcb)来维护进程的相关信息。
PCB进程控制块:
进程id
文件描述符表
进程状态: 初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码 (进程的概念)
信号相关信息资源。
用户id和组id
ps aux 返回结果里,第二列是进程id
进程的基本状态有5种:初始态(进程的准备阶段)、就绪态、运行态、挂起态、终止态。
三. 进程的函数操作
1. fork函数:父进程调用fork函数创建子进程
父子进程相同处:data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
不同处:进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
全局变量:读时共享,写时复制
pid_t fork(void)
参数:void
返回值:fork函数返回父子进程标志:
子进程:返回值为0
父进程:返回值大于0
获取父子进程的两个函数:
pid_t getpid() 获取当前进程id
pid_t getppid() 获取当前进程的父进程id
两个函数的返回值都是进程id(%d)
父子进程的注意事项:子进程创建成功后,父进程执行到哪,子进程就从哪执行;父子进程的执行顺序不一定,谁抢到cpu谁执行。
示例:循环创建多个子进程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int counter = 100;
int main(int argc, const char* argv[])
{
pid_t pid;
int i=0;
for(i=0; i<3; i++)
{
pid = fork();
//重点:如果不将子进程退出循环体,我们知道子进程会从这段代码后开始运行,也会循环产生多个自己的子进程。
if(pid == 0)
{
break;
}
}
// 父进程
if(i == 3)
{
counter += 100;
printf("parent process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
//等待子进程结束,回收子进程。
sleep(1);
}
// 子进程
else if(i == 0)
{
// 1th
counter += 200;
printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
}
else if(i == 1)
{
// 2th
counter += 300;
printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
}
else if(i == 2)
{
// 3th
counter += 400;
printf("child process, pid = %d, ppid = %d, %d\n", getpid(), getppid(), counter);
}
return 0;
}
2. 进程相关的命令
ps:
ps aux | grep "xxx"
pa aux | grep "xxx"
kill
查看信号:kill -l
杀死某个进程:kii -9 (SIGKILL)PID
3. exec函数族
功能:让父子进程执行不相干的操作,能够替换进程地址空间中的源代码.txt段
这样可以实现:执行另外一个程序不需要创建额外的地址空间,可以在一个运行的程序中调用另外一个程序。
execl函数:
int execl(const char *path, const char *arg, ……)
参数:
path:要执行的程序的绝对路径
变参arg:要执行的程序需要的草书
第一arg:占位,值不会影响后面的程序执行
后边的arg:命令的参数
参数写完之后:NULL
一般执行自己写的程序
返回值:
成功:无返回值
失败:-1
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc, const char* argv[])
{
printf("hello, world\n");
for(int i=0; i<3; ++i)
{
printf("parent i = %d\n", i);
}
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
// 子进程执行程序
if(pid == 0)
{
// execl("hello", "xxxx", NULL);
//execl("/home/kevin/hello", "xxxx", NULL);
//NULL这里是哨兵,表示可变变量的输入已经终止
//ls程序是用的子进程的地址空间
execl("/bin/ls","ls","-lah",NULL);
perror("execl");
exit(1);
}
for(int i=0; i<3; ++i)
{
printf(" i = %d\n", i);
}
return 0;
}
其他exec函数总结:
int execlp(const char *file, const char *arg, …)
参数:
const char*: 要加载的程序名字,该函数需要配合PATH环境变量来使用,
当PATH所有目录搜素后没有参数1则返回出错。
其他和execl函数类似。
该函数通常用来调用系统程序。如ls、date、cp、cat命令。
execlp这里面的p,表示要借助环境变量来加载可执行文件
int execle(const char *path, const char *arg, ……, char *const envp[])
参数:
path:执行程序的绝对路径 /home/lj/hello
arg:执行程序的参数
envp:用户自己指定的搜索目录,替代PATH
int execve(const char *path,char *const argv[], char *const envp[])
……
4. 孤儿进程和僵尸进程
孤儿进程:
父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。
僵尸进程:
子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。 kill 对其无效。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。子进程终止时,子进程残留资源PCB存放于内核中,PCB记录了进程结束原因**,进程回收就是回收PCB。回收僵尸进程,得kill它的父进程,让孤儿院去回收它。**
5. 进程回收函数
wait函数:回收子进程退出资源, 阻塞回收任意一个。
pid_t wait(int *status)
参数:(传出) 回收进程的状态。为传出参数
WIFEXITED(status):为非0,进程正常退出
WIFEXITED(status) :为真 调用 WEXITSTATUS(status)得到子进程 退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status):为真 调用 WTERMSIG(status)得到 导致子进程异常终止的信号编号。
返回值:成功: 回收进程的pid
失败: -1, errno
函数作用1: 阻塞等待子进程退出
函数作用2: 清理子进程残留在内核的 pcb 资源
函数作用3: 通过传出参数,得到子进程结束状态
一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。
示例:
waitpid函数: 指定某一个进程进行回收。可以设置非阻塞。
waitpid(-1, &status, 0) == wait(&status);
pid_t waitpid(pid_t pid, int *status, int options)
参数:
pid:指定回收某一个子进程pid
> 0: 待回收的子进程pid
-1:任意子进程
0:同组的子进程。
status:(传出) 回收进程的状态。
options:WNOHANG 指定回收方式为,非阻塞。
返回值:
> 0 : 表成功回收的子进程 pid
0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
-1: 失败。errno
一次wait/waitpid函数调用,只能回收一个子进程。父进程产生了多个子进程,wait会随机回收一个。
示例:指定一个子进程进行回收:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++)
{
pid = fork();
if (pid == 0)
{
// 循环期间, 子进程不 fork
break;
}
if (i == 2)
{
tmpid = pid;
printf("--------pid = %d\n", tmpid);
}
}
if (5 == i)
{
// 父进程, 从 表达式 2 跳出
sleep(5);
//wait(NULL); // 一次wait/waitpid函数调用,只能回收一个子进程.
//wpid = waitpid(-1, NULL, WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
//wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞等待
printf("i am parent , before waitpid, pid = %d\n", tmpid);
//wpid = waitpid(tmpid, NULL, WNOHANG); //指定一个进程回收, 不阻塞
wpid = waitpid(tmpid, NULL, 0); //指定一个进程回收, 阻塞回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
运行结果:
循环回收多个子进程:
// 回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 循环期间, 子进程不 fork
break;
}
}
if (5 == i) { // 父进程, 从 表达式 2 跳出
/*
while ((wpid = waitpid(-1, NULL, 0))) { // 使用阻塞方式回收子进程
printf("wait child %d \n", wpid);
}
*/
while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) { //使用非阻塞方式,回收子进程.
if (wpid > 0) {
printf("wait child %d \n", wpid);
} else if (wpid == 0) {
sleep(1);
continue;
}
}
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
编译运行,结果如下:
6. 进程间通信的方法
进程间通信(IPC):interprocess Communication
常用的有四种方式:(后面的章节介绍)
(1)管道-简单
(2)信号-系统开销小
(3)共享映射区-有无血缘关系的进程间通信都可以
(4)本地套接字-稳定
上一篇: jz2440 ARM9 开发板移植 mplayer
下一篇: Stream API 流