跳至主要內容

嵌入式学习之(七)| Linux 高级程序设计 2

Kevin 吴嘉文大约 9 分钟知识笔记Linux嵌入式学习

进程 (待续)

嵌入式自学开始 n 个月啦~ 为了自己的贾维斯 ?!!!

进程是程序的执行实例,进程是动态的。进程在内存中运行

程序是一些指令的有序集合,而进程是程序执行的过程,进程是程序的一次执行过程。

进程的状态是变化的,其包括进程的创建、调度和消亡。

笔记总结 课程链接:千峰嵌入式教程open in new window

进程的状态及转换

进程整个生命周期可以简单划分为三种状态: 就绪态: 进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间。 执行态: 该进程正在占用 CPU 运行。 等待态: 进程因不具备某些执行条件而暂时无法继续执行的状态。

进程的调度进制:

时间片轮转,上下文切换 多进程不是说一个进程执行完再执行另一个进程,而是 交替执行 的,一个进程执行一段时间,然后下一个进程在执行一段时间,依次类推,所有进程执行完之后再回到第一个今年初继续执行以此类推

image-20210225131736591
image-20210225131736591

进程控制块

进程控制块就是用于 保存一个进程信息的结构体 ,又称之为 PCB OS 是根据 PCB 来对并发执行的进程进行控制和管理的。系统在创建一个进程的时候会开辟一段内存空间存放与此进程相关的 PCB 数据结构。 PCB 是操作系统中最重要的记录型数据结构。PCB 中记录了用于描述进程进展情况及控制进程运行所需的全部信息。 PCB 是进程存在的唯一标志,在 Linux 中 PCB 存放在 task_struct 结构体中。 task_struct 结构体保存在 /usr/src/linux-headers-4.4.0-176-generic/include/linux/sched.h 一般在 1500 或者 1300 行左右,可以使用 vim -t 搜索

PCB 结构体中的部分数据

  • 调度数据 进程的状态、标志、优先级、调度策略等。
  • 时间数据 创建该进程的时间、在用户态的运行时间、在内核态的运行时间等。
  • 文件系统数据 umask 掩码、文件描述符表等。 内存数据、进程上下文、进程标识(进程号)

进程控制

进程号

每个进程都由一个进程号来标识,其类型为 pid_t,进程号的范围:0~32767

ubuntu 中查看系统中所有开启的进程

ps ajx

进程号(PID) 标识进程的一个非负整型数。 父进程号(PPID) 任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程 c 号称为父进程号(PPID)。 进程组号(PGID) 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各信号,关联的进程有一个进程组号(PGID) 。

Linux 操作系统提供了三个获得进程号的函数 getpid()、getppid()、getpgid()。

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程的进程号
pid_t getpgid(pid_t pid);
功能:获取当前进程所在进程组的 id

创建进程 fork

#include <unistd.h>
pid_t fork(void);
功能:在已有的进程基础上有创建一个子进程
参数:无
返回值:成功:
>0 子进程的进程号,标识父进程的代码区
0 子进程的代码区
失败:
1 返回给父进程,子进程不会创建

使用 fork 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间 地址空间: 包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号 等。 子进程所独有的只有它的进程号,计时器等。因此,使用 fork 函数的代价是很大的。 fork 函数执行完毕后父子进程的空间示意图:

创建子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//通过 fork 函数创建一个子进程
//不推荐不区分创建子进程
#if 0
//注意:主要执行一次 fork,就会在原有的进程基础上创建一个新的子进程
//而且如果 fork 之后不区分父子进程的代码区,则后面所有的代码都会执行
fork();
printf("hello world\n"); \\会打印两遍

while(1)
;
#endif

//区分进程的写法
//通过 fork 函数的返回值来区分父子进程的独立的代码区
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fail to fork");
return1;
}
else if(pid > 0) //此处 pid 是子进程的进程号,父进程的代码区(在父进程中运行)
{
while(1)
{
printf("parent: pid = %d, ppid = %d\n", getpid(), getppid());
printf("pid = %d\n", pid);
printf("this is a parent process\n");
sleep(1);
printf("****************\n");
}}
else //子进程的代码区
{while(1)
{
printf("son: pid = %d, ppid = %d\n", getpid(), getppid());
printf("this is a son process\n");
sleep(1);
printf("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐\n");} 
}
return 0;
}

父子进程是来回交替执行的,谁先运行,谁后运行是不确定的,不要认为父进程执行完之后才会执行子进程

  • 子进程会复制父进程 fork 之前的所有内容

  • 但是 fork 之后,父子进程完全独立,所以不管双方怎么改变(堆区、栈区、数据区等),都不会收对方影响

  • 子进程会继承父进程的一些公有的区域,不如磁盘空间,内核空间

  • 文件描述符的偏移量保存在内核空间中,所以父进程改变偏移量,则子进程获取的偏移量是改变之后的

进程挂起

sleep(2);

进程的等待

取出子进程的退出信息 WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零。 WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在 status 变量的 8~16 位。在用此宏前应先用宏 WIFEXITED 判断子进程是否正常退出,正常退出才可以使用此宏。 注意: 此 status 是个 wait 的参数指向的整型变量。

 #include <sys/types.h>
 #include <sys/wait.h>
 int main(int argc, char *argv[])
 {
 pid_t pid;

pid=fork();
 if(pid<0)
{
 perror("fail to fork");
 return1;
 }
 if(pid == 0)
 {
 int i = 0;
 for(i=0;i<5;i++)
 {
 printf("this is son process\n");
 sleep(1);
 }
 //使用 exit 退出当前进程并设置退出状态
 exit(2);
 }
 else
 {
 //使用 wait(NULL)在父进程中阻塞等待子进程的退出
 //不接收子进程的退出状态
 //wait(NULL);

 //接收子进程的退出状态,子进程中必须使用 exit 或者_exit 函数退出进程是发送退出状态
 int status = 0;
 wait(&status);

 if(WIFEXITED(status) != 0)
 {
 printf("The son process return status: %d\n", WEXITSTATUS(st
atus));
 }
 printf("this is father process\n");}
 return 0;
 }
  • waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status,int options)
参数:
pid:指定的进程或者进程组
pid>0:等待进程 ID 等于 pid 的子进程。
pid=0:等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid
不会等待它。
pid=1:等待任一子进程,此时 waitpid 和 wait 作用一样。
pid<1:等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值

status:保存子进程退出时的状态信息
options:选项
0:同 wait,阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回。
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。
(跟踪调试,很少用到)
返回值:
成功:返回状态改变了的子进程的进程号;如果设置了选项 WNOHANG 并且 pid 指定的进程存
在则返回 0
失败:返回‐1。当 pid 所指示的子进程不存在,或此进程存在,但不是调用进程的子进
程,waitpid 就会出错返回,这时 errno 被设置为 ECHILD
waitpid(pid, NULL, 0);
printf("this is father process\n");

进程的终止

进程的终止

使用 return return 除了可以返回值以外,在主函数中使用可以退出进程,但是在子函数中使用 只能退出当前函数

使用 exit exit 可以退出一个进程并且可以刷新缓冲区.exit 为库函数,而_exit 为系统调用。在子函数中出错后使用来推出整个进程。

使用、_exit _exit 可以退出一个进程,但是不会刷新缓冲区

exit 函数
1 #include <unistd.h>
2 void _exit(int status);
3 功能:退出当前进程
4 参数:
5 status:退出状态,由父进程通过 wait 函数接收这个状态
6 一般失败退出设置为非 0
7 一般成功退出设置为 0
8 返回值:
9
_exit 函数
1 #include <stdlib.h>
2 void exit(int status);
3 功能:退出当前进程
4 参数:
5 status:退出状态,由父进程通过 wait 函数接收这个状态
6 一般失败退出设置为非 0
7 一般成功退出设置为 0
8 返回值:
9

进程退出清理

  • atexit 函数在进程结束时才会去执行参数对应的回调函数
  • atexit 多次调用后,执行顺序与调用顺序相反
#include <stdlib.h>
int atexit(void (*function)(void));\
功能:注册进程正常结束前调用的函数,进程退出执行注册函数
参数:
function:进程结束前,调用函数的入口地址。
一个进程中可以多次调用 atexit 函数注册清理函数,
正常结束前调用函数的顺序和注册时的顺序相反 c
返回值:
成功:0
失败:非 0
int main(int argc, char *argv[])
{
atexit(clear_fun1);
atexit(clear_fun2);c
atexit(clear_fun3);
printf("process exit 3 sec later!!!\n");
sleep(3);
return 0;
}

vfork

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
功能:vfork 函数和 fork 函数一样都是在已有的进程中创建一个新的进程,但它们创建的子
进程是有区别的。
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID
失败:‐1

fork 和 vfork 函数的区别:

  • vfork 保证子进程先运行 ,在它调用 exec 或 exit 之后,父进程才可能被调度运行。

  • vfork 和 fork 一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 exec(或 exit),于是也就不访问该地址空间。

  • 相反,在子进程中调用 exec 或 exit 之前,它在父进程的地址空间中运行,在 exec 之后子进程会有自己的进程空间。

  • 使用 vfork 创建完子进程,在子进程执行 exit 或者 exec 之前,父子进程共有同一块地址空间

未完待续...

上次编辑于:
贡献者: kevinng77