Linux进程控制

1.进程创建

fork()为什么有两个返回值?

  • (1)fork()会创建子进程,两个进程都会指向return语句,所有有两个返回值。
  • (2)fork()之后if和else会同时执行,id有两份,子进程查第一份,父进程查第二份。
  • (3)创建一个进程,就等于多了一个PCB,地址空间,页表,映射关系。
  • (4)fork()之后,父子进程代码共享,数据以写时拷贝的形式各自私有一份。(速度更快,效率更高,节省资源)
  • (5)子进程返回0,父进程返回子进程的pid。

数据区(data seg):也称全局区或者静态区,根据名称我们就应该知道用来存放一些全局的东西,比如我们经常用到的静态变量、全局变量等都会存放到数据区,此区域上的东西都被全局所共享。比如我们可以采取类名.的方式就可以访问到方法,这就是所谓的静态方法,存放到数据区的。

代码区:存放程序编译后可以执行代码的地方。比如执行代码时写的While语句、if条件语句等,都会存放到此。

二.进程终止

1.程序退出的场景

  • (1)代码运行完毕,结果正确
  • (2)代码运行完毕,结果不正确
  • (3)代码异常终止

进程终止就要把这个进程所占的物理内存资源释放掉,页表,地址空间都不要。
2.进程常见退出方法
正常终止:

  • (1)从main函数返回

return退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。
只有进程终止的时候,才会刷新缓冲区。

  • (2)调用exit

在任何时候调用exit,都表示当前进程退出
#incude<unistd.h>
void exit(int status);
exit后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 at_exit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
  • (3)_exit

#include<unistd,h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
它终止时不会刷新缓冲区

异常终止:
ctrl + c,信号终止

echo $? 查看最近一个进程的退出码。

四.进程等待

1.进程等待的必要性

  • (1)子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • (2)另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
  • (3)父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • (4)父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

2.进程等待的方法

  • (1)wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

//进程等待回收子进程的内存资源,避免内存泄漏。
  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/wait.h>
  5 #include<sys/types.h>
  6 
  7 using namespace std;
  8 
  9 
 10 int main()
 11 {
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         //子进程
 16         cout << "I am a child...." << endl;
 17 
 18         sleep(5);
 19         //sleep(10);
 20         exit(1);
 21     }
 22     else
 23     {//父进程
 24         wait(NULL);
 25         cout << "I am a father ..." << endl;
 26         //sleep(20);
 27         //wait(NULL);
 28 
 29         //sleep(5);
 30 
 31     }

  • (2)waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数: pid: Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) >WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。
options的等待方式有两种:
(1)阻塞式:(pid = 0)调用scanf时,没有输入数据,把进程阻塞了,设置为非r状态,进入等待序列
唤醒:将状态设为r,将PCB放入运行队列中。
非阻塞式:当你要等某个人的时候,不能因为等它,而阻止自己发生的事。

  • (1)如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • (2)如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • (3)如果不存在该子进程,则立即出错返回。

3.获取子进程的status

  • (1)wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • (2) 如果传递NULL,表示不关心子进程的退出状态信息。
  • (3) 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • (4)status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

在这里插入图片描述
当收到信号时,先看是正常还是异常,如果正常就看退出码,如果异常的话就不用看。

 37 int main()
 38 {
 39     pid_t id = fork();
 40     if(id == 0)
 41     {
 42         //child
 43         printf("child : pid : %d, ppid : %d \n",getpid(),getppid());
 44         sleep(5);
 45         exit(13);
 46     }
 47     else
 48     {
 49         //father
 50         printf("father : pid : %d , ppid : %d\n",getpid(),getppid());
 51         int status = 0;
 52 
 53 
 54         while(1)
 55         {
 56             int ret = waitpid(id, &status, WNOHANG);
 57 
 58             if(ret < 0)
 59             {
 60                 printf("wait error , wait ret : %d\n",ret);
 61                 break;
 62             }
 63             else if(ret > 0)
 64             {
 65                 printf("wait success... : %d\n",ret);
 66                 printf("exit status ... : %d\n",(status >> 8)&0xff);
 67                 printf("exit signal ... : %d\n",status & 0x7f);
 68             }
 69             else
 70             {
 71                 sleep(1);
 72                 printf("parent wait again\n");
 73             }
 74         }
 75         sleep(5);
 76     }
 77     return 0;
 78 }

四.进程程序替换

1.替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

2.替换函数

include <unistd.h>`
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
int execle(const char *path, const char *arg, …,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

3.函数解释

  • (1)这些函数如果调用成功则加载新的程序从启动代码开始执行,不在返回。
  • (2)如果调用出错则返回-1。
  • (3)所以exec函数只有出错的返回值而没有成功的返回值。

4.命名理解

  • (1)l(list):表示参数采用列表。
  • (2)v(vector):参数用数组。
  • (3)p(path):有p自动搜索环境变量PATH。
  • (4)e(env):表示自己维护环境变量。
#include<iostream>
#include<unistd.h>

using namespace std;
int main()
{
	execl("/usr/bin/ls", "ls", "-a", "-l", "-i", NULL)
	//execl中第一个参数带得是命令的路径。
	
	execlp("top", "top", NULL);
	//第一个top是你要执行谁,第二个top是你要怎样执行。
	
	char* argv[] = {"ls", "-a", "-l", "-i", NULL};
    execv("/usr/bin/ls", argv);
    execvp("ls", argv);
    
	return 0;
}

调用一个接口阻塞了,怎么办?
进程想从键盘里面输入数据,如果不输内容,程序就会在那里卡住,把一个进程卡住,不是进程不运行了,而是把进程设置为非r状态,然后放进等待队列中;如果事件就绪,操作系统就会把进程唤醒。
bash怎么把命令跑起来?
bash读取命令行上的所有命令,先fork()创建子进程,bash等子进程,子进程程序替换,子进程就跑传入的命令。

//简单的shell书写
#include <iostream>
  2 #include <unistd.h>
  3 #include <cstdio>
  4 #include <string>
  5 #include <cstring>
  6 #include <stdlib.h>
  7 #include <sys/wait.h>
  8 #include <sys/types.h>
  9 
 10 
 11 using namespace std;
 12 #define NUM 1024
 13 
 14 int main()
 15 {
 16     char buff[1024] = {0};
 17     for(;;)
 18     {
 19 
 20         string tips = "[xxx@localhost YYY]# ";
 21         cout << tips;
 22 
 23         fgets(buff, sizeof(buff) - 1,stdin);
 24         buff[strlen(buff) - 1] = 0;
 25 
 26         //提取数据ls -a -l -o
 27         char * argv[NUM];
 28         argv[0] = strtok(buff," ");
 29         int i = 0;
 30         while(argv[i])
 31         {
 32             i++;
 33             argv[i] = strtok(NULL," ");
 34         }
 35 
 36         pid_d id = fork();
 37         if(id == 0)
 38         {
 39             //child
 40             cout << "child running ...... " << endl;
 41             execvp(argv[0],argv);
 42             exit(123);
 43         }
 44         else
 45         {
 46             int status = 0;
 47             waitpid(id,&status,0);
 48             cout << "Exit Code " << WEXITSTATUS(status) << endl;
 49         }
 50 
 51     }
 52     return 0;
 53 }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值