嵌入式Linux笔记 —— 《进程》(持续更新哦~)

基本概念:
程序 - 主要指存储在磁盘上的文件
进程 - 主要指在内存中运行的程序

在windows系统中,我们常用调用任务管理器进行查看系统进程(Ctrl+Alt+Delete)
在unix/linux系统中,我们常用ps -[选项] 命令来查看系统进程

PID ---- 进程的编号
TTY ---- 终端的次要装置号码
TIME ---- 消耗CPU的时间
CMD ---- 具体的命令以及参数
ps -aux : 表示显示所有包括其他使用者的进程
ps -aux|more : 表示分屏显示所有的进程信息

USER ---- 属主信息(掌握)
PID ---- 进程号(重点)
%CPU ---- 占用CPU的百分比
%MEM ---- 占用内存的百分比
VSZ ---- 虚拟内存大小
RSS ---- 物理内存大小
TTY ---- 终端的次要装置号码
STAT ---- 进程的状态(重点)
START ---- 进程的启动时间
TIME ---- 消耗CPU的时间
COMMAND ---- 具体的进程名称以及选项(掌握)
进程的常见状态主要有:

  1. S ---- 休眠状态
  2. s ---- 进程的领导者(表示拥有子进程)
  3. Z ---- 僵尸进程(进程已结束,资源没有回收)
  4. R ---- 正在运行的进程
  5. O ---- 可运行的状态(就绪状态)
  6. T ---- 挂起状态
  7. < ---- 优先级高的进程
  8. N ---- 优先级低的进程

ps -ef : 表示使用全格式的方式显示进程信息
ps -ef|more : 表示分屏显示进程的信息


PID ---- 进程号
PPID ---- 父进程的进程号
kill -9 进程号 : 表示杀死指定的进程

进程管理:
目前主流的操作系统都支持多进程
如果进程A启动了进程B,那么进程A叫做进程B的父进程,进程B叫做进程A的子进程
操作系统的多进程组成树型结构,其中进程0是系统内部的进程,负责启动进程1(init进程)和进程2,其他所有的进程都是由进程1和进程2直接/间接的启动起来

PID ---- 进程号:是进程在操作系统中的唯一标识

进程号的分配采用延迟重用的策略
即:在每一个时刻都可以保证进程号唯一. (进程号是以递增的形式分配的,当进程号分配到头的时候,才从0开始分配未被占用的进程号)

如何获取当前进程的进程号?
  1. getpid() ----主要用于获取当前进程的进程号
    ( 返回值类型:pid_t => int 一般从0开始)
  2. getppid() ----主要用于获取当前进程父进程的进程号( 返回值类型:pid_tpid_t => int 一般从0开始)
  3. getuid() -主要用于获取用户的ID
    (返回值类型:uit_tuid_t => unsigned int)
  4. getgid() -主要用于获取用户所在用户组的ID
    (返回值类型:gid_tuid_t => unsigned int)
示例代码:
//获取各种ID信息                                                            
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
	 printf("当前进程的进程号是:%d\n",getpid());
	 printf("当前进程的父进程是:%d\n",getppid());
	 printf("当前用户的编号是:%d\n",getuid());
	 printf("当前用户的用户组ID是:%d\n",getgid());
	 
	 return 0;
}
运行结果:

进程的创建

1. fork函数

函数功能:
主要用于以复制正在运行进程的方式来创建新的进程,其中新进程叫做子进程,正在运行的进程叫做父进程

返回值:
函数调用成功时:父进程返回子进程的PID,子进程返回0
函数调用出错时:父进程返回-1,子进程没有被创建

示例代码:
//使用fork函数创建子进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
  printf("main函数开始执行\n");
  
  pid_t pid = fork();
  
  if( pid == -1)
 {
   perror("fork"),exit(-1);
 }
 
  printf("main函数结束%d\n",pid);
  return 0;
}
运行结果:


main函数开始执行
main函数结束4087 //由父进程打印
main函数结束0 //由子进程打印

2. fork创建子进程的工作方式
a.fork函数之前的代码,父进程执行一次
b.fork函数之后的代码,父子进程各自执行一次
c.fork函数的返回值,父子进程各自返回一次

对上述代码作修改,让父子进程各自执行不同的工作

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
  printf("main函数开始执行\n");
  
  //创建子进程
  pid_t pid=fork();
  
  //判断是否创建成功
  if(-1 == pid)
 {
   perror("fork"),exit(-1);
 }

  //如果是子进程
  if(0 == pid)
 {
   printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
 }
  else
 {
   printf("我是父进程%d,我的子进程是:%d\n",getpid(),pid);
 }
  return 0;
}
运行结果:


对上述代码再作修改(父进程先于子进程结束)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int main(void)
{
  printf("main函数开始执行\n");
  //创建子进程
  pid_t pid=fork();
  //判断是否创建成功
  if(-1==pid)
 {
   perror("fork"),exit(-1);
 }
  //printf("main函数结束%d\n",pid);
  //如果是子进程
  if(0 == pid)
 {
   printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
   //睡眠1秒
   sleep();
   printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
 }
  else
 {
   printf("我是父进程%d,我的子进程是:%d\n",getpid(),pid);
 }
  return 0;
}
运行结果:


3. fork创建的父子进程之间的关系
a.父进程启动子进程,父子进程同时启动,如果子进程先结束,会给父进程发信号等,让父进程帮助子进程回收资源

b.如果父进程先结束,子进程变成孤儿进程,子进程会变更父进程(重新指定init进程为新的父进程),init进程也叫做孤儿院

c.如果子进程先结束,但是父进程由于各种原因并没有收到子进程发来的信号,也就是没有帮助子进程回收资源,那么子进程会变成僵尸进程

注意: fork()函数创建子进程之后,父子进程的先后执行次序是不确定的,由操作系统负责调度 (两个进程各自运行,互不干扰)先执行父进程还是先执行子进程是由系统决定的(我们是不确定的)

4.fork函数创建的父子进程之间的内存资源关系

进程的内存区域划分:代码区,全局区,堆区,栈区

示例:观察父子进程间的内存关系
//观察父子进程之间的内存资源关系
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

int var1 = 1; //全局变量  全局区

int main(void)
{
   int var2=1; //局部变量  栈区
   
   //pc指向堆区 pc本身在栈区
   char* pc=(char*)malloc(10*sizeof(char));
   
   strcpy(pc,"hello");
   
   //创建子进程
   pid_t pid=fork();
   
   if(-1 == pid)
  {
    perror("fork"),exit(-1);
  }
  
  //子进程
  if(0 == pid)
 {
    int var3=2;
    var1=2;
    var2=2;
    pc[0]='H';
    printf("子进程中:var1=%d,var2=%d,var3=%d,pc=%s\n",var1,var2,var3,pc);
    exit(0); //终止子进程
 }
 
 	sleep(1); //保证子进程早于父进程结束
 	printf("父进程中:var1=%d,var2=%d,pc=%s\n",var1,var2,pc);
 
 return 0;
}                     
运行结果:


使用fork函数创建的子进程会复制父进程中除了代码区(代码区没有拷贝的必要)之外的其他内存区域,而代码区和父进程共享

5. 进程扩展

a.调用fork函数两次(进程数=2的fork()个数的次方)
fork();
fork();

一共4个进程:1个父进程+2个子进程+1个孙子进程

b.如何创建出3个进程呢?
fork();
if(0!=pid) //父进程才能执行
{
fork();
}

一共3个进程:1个父进程+2个子进程

c.俗称:"fork炸弹",会让电脑死机(早期的电脑病毒)
while(1)
{
fork();
}

6.进程的终止

  1. 正常终止进程的方式:
    a.在main函数中执行了return 0;
    b.调用exit()函数终止进程
    c.调用_exit()/Exit()函数终止进程
    d.最后一个线程返回
    e.最后一个线程调用了pthread_exit()函数
  2. 非正常终止进程的方式:
    a.采用信号终止进程,比如:ctrl+c
    b.最后一个线程被其它线程调pthread_cancel()取消
6.1进程终止函数的比较
_exit()和_Exit()函数

#include<unistd.h>
void _exit(int status); // =>uc库的函数
函数功能:
主要用于引起正常进程的终止,并且将参数值(status)&0377(相当于取低八位)的结果返回给父进程 在终止进程的期间会调用由atexit()/on_exit()函数注册(准备)过的函数
#include<stdlib.h>
void _Exit(int status); // =>标c库的函数
函数功能: 主要用于立即终止正在运行的进程,参数status的值会被返回给父进程, 父进程调用wait系列函数进行获取,该参数作为终止进程的退出状态信息(也就是退出的原因)

exit()函数

#include<stdlib.h>
void exit(int status);
函数功能: 主要用于引起正常进程的终止,并且将参数值(status)&0377(相当于取低八位)的结果返回给父进程 在终止进程的期间会调用由atexit()/on_exit()函数注册(准备)过的函数

atexit()函数

#include<stdlib.h>
int atexit(void (*function)(void));//参数是函数指针,实参传函数的地址(也就是函数名)
函数功能:
主要用于对参数指定的函数进行注册/准备,而被该函数注册过的函数会在程序调用exit函数的期间被调用,或者在main函数执行return之后被调用

各种终止进程函数的调用示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

//自定义注册函数
void show(void)
{
  printf("我就是准备的注册函数\n");
}
int main(void)
{
  //使用atexit函数进行注册
  atexit(show);
  printf("main函数开始执行\n");
  _exit(100);//_exit() _Exit()不引起show函数的调用
  printf("main函数结束\n");
  
  return 0;
}
运行结果:

//各种终止进程函数的调用                                                    
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

//自定义注册函数
void show(void)
{
  printf("我就是准备的注册函数\n");
}
int main(void)
{
  //使用atexit函数进行注册
  atexit(show);
  printf("main函数开始执行\n");
  exit(100);  //exit()会引起show函数的调用
  printf("main函数结束\n");
  return 0;
}
运行结果;

//各种终止进程函数的调用                                                    
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//自定义注册函数
void show(void)
{
  printf("我就是准备的注册函数\n");
}
int main(void)
{
  //使用atexit函数进行注册
  atexit(show);
  printf("main函数开始执行\n");
  printf("main函数结束\n");
  
  return 0;  return//引起show函数的调用
}
运行结果:


7.进程的等待

wait函数

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);

函数功能:
主要用于挂起当前正在运行的进程进入阻塞状态,直到有一个子·进程终止为止(进程的终止作为进程状态发生改变的一种情况) 如果参数不为空,那么wait函数会将获得的状态信息存储到status指向的int类型空间中 成功返回终止的子进程ID,失败返回-1

WIFEXITED(status) => 判断子进程是否正常终止.若子进程正常终止(子进程调用exit/_exit函数或从main函数中执行了return均为子进程的正常终止),则返回true
WEXITSTATUS(status) => 获取子进程的退出状态信息

//wait函数的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(void)
{
  //1.创建子进程
  pid_t pid=fork();
  if(-1==pid)
 {
    perror("fork"),exit(-1);
 }
 
 //2.子进程启动,占用10秒后终止
  if(0==pid)
 {
   printf("子进程%d开始运行\n",getpid());
   sleep(10);
   printf("子进程结束\n");
   exit(100); 
 }
 
 //3.父进程等待子进程结束,并且获取退出信息
   int status=0;
   int res=wait(&status);
   if(-1==res)
  {
    perror("wait"),exit(-1);
  }
  printf("子进程总算结束了,status=%d,res=%d\n",status,res);
  return 0;
}   
运行结果:


注:status中只是包括了子进程的退出码100,除了100之外还包括了其它的一些信息,所以打印出25600.怎样把status中子进程终止时的退出码100给获取出来?即如何获取真正的退出码?=>使用宏WEXITSTATUS(status)

对上述程序作修改:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(void)
{
  //1.创建子进程
  pid_t pid=fork();
  if(-1==pid)
 {
    perror("fork"),exit(-1);
 }
 //2.子进程启动,占用10秒后终止
  if(0==pid)
 {
   printf("子进程%d开始运行\n",getpid());
   sleep(10);
   printf("子进程结束\n");
   exit(100); 
 }
 //3.父进程等待子进程结束,并且获取退出信息
   int status=0;
   int res=wait(&status);
   if(-1==res)
  {
    perror("wait"),exit(-1);
  }
  printf("子进程总算结束了,status=%d,res=%d\n",status,res);
  if(WIFEXITED(status));
 {
   printf("子进程正常终止,子进程的退出状态信息是:%d\n",WEXITSTATUS(status));
 }
  return 0;
} 
运行结果:

waitpid函数

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int* status,int options);
函数功能:主要用于等待指定的子进程状态发生改变

第一个参数:进程号(指定要等待哪一个进程)

  1. <-1:等待进程组ID为pid绝对值的任意一个子进程(了解)
  2. -1:等待任意一个子进程(重点掌握)
  3. 0:等待和调用进程在同一个进程组的任意一个子进程(了解)
  4. >0:等待进程号为pid的子进程(重点掌握)

第二个参数:指针变量,用于获取子进程的状态信息

第三个参数:等待的方式,默认给0即可

返回值:成功返回等到的子进程ID,失败返回-1

//waitpid函数的使用                                                         
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(void)
{
 //启动3个进程:1个父进程+2个子进程
  pid_t pid1,pid2;
  pid1=fork();
  if(-1==pid1)
 {
   perror("fork"),exit(-1);
 }
 //父进程
  if(0!=pid1)
 {
   //再次创建子进程
    pid2=fork();
 }
 //子进程一
 if(0==pid1)
{
  printf("子进程一%d开始启动\n",getpid());
  sleep(5);
  printf("子进程一结束\n");
  exit(100);
 }
 //子进程二
 if(0==pid2)
{
  printf("子进程二%d开始启动\n",getpid());
  sleep(10);
  printf("子进程二结束\n");
  exit(200);
 }
 
 printf("父进程开始等待...\n");
 int status=0;
 //等待任意一个子进程,子进程一先结束
 int res=waitpid(-1,&status,0);                   
 if(-1==res)
{
  perror("waitpid"),exit(-1);
} 

 printf("等到的子进程是:%d,得到的状态信息是:%d\n",res,WEXITSTATUS(status));
 return 0;
}                      
运行结果:


对上述程序作修改:

//waitpid函数的使用                                                         
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(void)
{
 //启动3个进程:1个父进程+2个子进程
  pid_t pid1,pid2;
  pid1=fork();
  if(-1==pid1)
 {
   perror("fork"),exit(-1);
 }
 //父进程
  if(0!=pid1)
 {
   //再次创建子进程
    pid2=fork();
 }
 //子进程一
 if(0==pid1)
{
  printf("子进程一%d开始启动\n",getpid());
  sleep(5);
  printf("子进程一结束\n");
  exit(100);
 }
 //子进程二
 if(0==pid2)
{
  printf("子进程二%d开始启动\n",getpid());
  sleep(10);
  printf("子进程二结束\n");
  exit(200);
 }
 printf("父进程开始等待...\n");
 int status=0;
 //等待进程号为pid1的进程,子进程1
 int res=waitpid(pid1,&status,0);                   
 if(-1==res)
{
  perror("waitpid"),exit(-1);
}
 printf("等到的子进程是:%d,得到的状态信息是:%d\n",res,WEXITSTATUS(status));
 return 0;
}            
运行结果:

有错误的地方希望大家及时的指出来哦~
看完后觉的有所收获的小伙伴点个赞嘛~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字梦想家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值