进程控制——Linux

vim批量化注释

注释:ctrl + v, hjkl选中区域,shift+i,然后按esc,再按// 就加上注释了。

去注释:小写,ctrl + v,选中区域,按d。

如何创建进程

fork函数的创建子进程

#include <stdio.h>
#include <unistd.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    while(1)
    {
      printf("我是子进程, pid:%d, ppid: %d\n", getpid(), getppid());
      sleep(1);
    }
  }
  else
  {

  while(1)
  {                                                                 
    printf("我是父进程, pid:%d, ppid: %d\n", getpid(), getppid());
    sleep(1);
  }

  }
}

for创建时,内核做了什么?

fork之后

#include <stdio.h>
#include <unistd.h>
int main()
{  
  printf("我是一个进程: pid: %d\n", getpid());
  fork();  
  printf("我依旧是一个进程:pid:%d\n", getpid());
   
  sleep(1);      
}                                

fork之后,是否只有fork之后的代码是被父子进程共享的?

进程具有独立性:代码和数据必须独立的。

代码只能读取。

写时拷贝。

那么所谓的代码共享到底是什么?

最准确的说法是:fork之后,父子共享所有的代码!意味着整个程序代码都会共享。

子进程执行的后续代码 不等于 共享的所有代码,只不过子进程只能从这里开始执行!

为什么呢?为什么子进程只能从这里执行?

因为cpu的寄有个叫做eip的程序计数器,他会保存当前正在执行指令的下一条指令。

eip程序计数器会拷贝给子进程,子进程便从该eip所指向的代码处该是执行了!

结论就是:fork后代码是共享的,数据是以写时拷贝的方式进行拷贝的。

进程

fork之后
进程 = 内核的进程数据结构 + 进程的代码和数据

创建子进程的内核数据结构(struct task_struct + struct mm_struct + 页表) + 代码继承父进程,数据以写时拷贝的方式,来进行共享或者独立!

写时拷贝

深浅拷贝就可以理解为写时拷贝。

前提就是写的时候再拷贝。

通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

写时拷贝本身就是由OS的内存管理模块完成的。

优点:
1.只会拷贝父子修改的,变相的,就是拷贝数据的最小成本。拷贝的成本依旧存在。那么为什么要等到写的时候再拷贝?

2.延迟拷贝策略,只有真正的时候,才给你用!最大的意义在于:你想要,但是不立马使用的空间,先不给你,意味着可以先给别人用!

为什么要写时拷贝?

为什么不在创建进程的时候,就把数据分开不行吗?

这种方案本身来说是可以的。但是不是最优的。

为什么不选择它呢?

1.父进程的数据,子进程不一定全用。

比如全局变量,父进程定义了10个,子进程只需要用1个,剩下的就全浪费了空间。

2.即便10个全局变量都用,但不一定写入,可能只做读取,所以有浪费空间的嫌疑。

3.假如存在多个不需要修改的变量。那么还会造成空间浪费,不需要修改的数只需要存在一份即可。

最理想的情况,只有会被父子修改的数据,进行分离拷贝。不需要修改的共享即可。但是这种技术是不可实现的。

4.如果fork的时候,就无脑拷贝数据给子进程,无疑会增加fork()的成本。(内存和时间)

所以最终采用写时拷贝。

终止进程

常见的进程退出:

  1. 代码跑完,结果正确
  2. 代码跑完,结果不正确
  3. 代码没跑完,程序异常了

关于终止的正确认识

在写C语言时,经常会写return 0,

1.写return 0给谁return?

2.为何是0?其他值可以吗?

返回值为0,代表进程代码跑完,结果是否正确。

非0,代表失败。所以:非零标识不同的原因!

return的值表征了进程退出的信息。

将来这个退出信息是要给父进程进行读取的。

父进程根据这个退出码,来确定写的对不对。

关于终止的常见做法

1.在main函数中return,为什么其他函数不行?

2.在自己的代码任意地点中,调用exit(),代表进程退出。

exit()函数里面的参数也是退出码,代表各种信息。

_exit和exit

二者的区别在于:
1._exit直接终止进程,不会有任何刷新操作
2.exit终止进程,刷新缓存区

关于终止,内核做了什么

进程 = 内核结构 + 进程代码 和 数据

将进程设置为x状态,然后释放数据。

内核数据结构比如task_struct和mm_struct可能不会被释放。因为有内核的数据结构缓存池,叫做slab分派器。

echo $?

echo是内置命令,$?在bash中,代表得到最近一次执行完毕时,对应进程的退出码!

一般而言,失败的非零值该怎么设置,也就是返回值该怎么写,以及默认表达的含义。

进程等待

为什么要进行进程等待?

1、主要是为了解决内存泄漏的问题。防止进入僵尸状态。
2、获取子进程的退出状态

如何等待?

wait

在这里插入图片描述

1.父进程直接调用wait即可。

监控脚本,输入done即可运行

while : ; do ps ajx | head -1 && ps ajx | grep myproc | grep -v grep; echo "---------------------------------"; sleep 1; 
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
      //孩子
      while(1)
      {
        printf("我是子进程,我正在运行......pid: %d\n",getpid());
        sleep(1);
      }
  }
  else{
        printf("我是父进程:pid : %d 我准备等待子进程了\n", getpid());
       sleep(20);
        pid_t  ret = wait(NULL);

        if(ret <0)
        {
          printf("等待失败\n");
        }
        else{
          printf("等待成功: result:%d\n",ret);
        }
        sleep(20);
  }
}

wait()的方案可以解决回收子进程的Z状态,让子进程进入X状态。

waitpid

wait和waitpid都是系统调用,都是从子进程的task_struct中拿出子进程的退出码。

等待任意一个退出的子进程

pid_t waitpid(pid_t pid, int* status, int options);

参数列表:
1.pid大于0,是几,就代表等待哪一个子进程, pid = 123,指定等待123.

-1:等待任意进程。

2.options为0,代表阻塞等待。

3.status代表一个输出型参数。
什么是输出型参数?

输出型参数的意思就是通过调用该函数,从函数内部拿出特定的数据。也就是拿出来一个整数。

status只需要关心该整数的低16比特位,低16比特位会被分为三部分。status右移八位并不影响status本身的值。

次低8位,可以得到退出码。
最低八位,是异常状态,也就是异常退出。

返回值:
pid_t:
大于0 :等待子进程成功,返回值就是子进程的pid
小于0:等待失败

终止信号:
进程退出,如果异常退出,是因为这个进程收到了特定的信号!!

kill -l是Linux中的信号,后面需要学。没有0号信号!!

一旦进程出现异常,只关心退出信号,关注退出码毫无意义。

阻塞等待和非阻塞等待

waitpid()回收状态。

假如子进程就是不退出,则父进程只能进行阻塞等待。当我们调用某些函数的时候,因为条件不就绪,需要我们阻塞等待,本质:就是当前进程变成阻塞状态,等条件就绪的时候,再被唤醒。

进程间的替换

为了让子进程执行一个全新的程序,所以才需要进程间替换。

什么是进程间的替换?

概念:让子进程执行新的程序

原理:
1.将磁盘中的程序,加载到内存结构中

2.重新建立页表映射,设执行程序替换,就重新建立谁的映射,结果:让我们父子进程彻底分离,并让子进程执行一个全新的程序。

为什么要进程间替换?

我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两个种类事情。
1.让子进程执行父进程的代码片段(服务器代码)

2.让子进程执行磁盘中一个权限的程序(shell,想让客户端执行相应的程序,或者通过我们的进程,执行其他人写的进程代码)。

例如用C++调用其他人写的python、java等。

怎么编码?

父进程(单进程)execl简单替换

注意:如果一旦替换成功,是将当前进程的代码和数据全部替换了。execl这一行的下面的代码全部被替换了。

程序替换不需要判断返回值,因为只要替换成功,就不会有返回值,如果没有成功,就会执行下面的代码,执行后面的代码就会知道执行失败了。

1.找到要替换的程序路径

2.然后输入选项,以null结束。

子进程的替换

int execl(const char *path, const char *arg, ...);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如执行ls程序
execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

简易shell制作

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后的最大个数
int main()
{
	char cmd[LEN]; //存储命令
	char* myargv[NUM]; //存储命令拆分后的结果
	char hostname[32]; //主机名
	char pwd[128]; //当前目录
	while (1){
		//获取命令提示信息
		struct passwd* pass = getpwuid(getuid());
		gethostname(hostname, sizeof(hostname)-1);
		getcwd(pwd, sizeof(pwd)-1);
		int len = strlen(pwd);
		char* p = pwd + len - 1;
		while (*p != '/'){
			p--;
		}
		p++;
		//打印命令提示信息
		printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);
		//读取命令
		fgets(cmd, LEN, stdin);
		cmd[strlen(cmd) - 1] = '\0';
		//拆分命令
		myargv[0] = strtok(cmd, " ");
		int i = 1;
		while (myargv[i] = strtok(NULL, " ")){
			i++;
		}
		pid_t id = fork(); //创建子进程执行命令
		if (id == 0){
			//child
			execvp(myargv[0], myargv); //child进行程序替换
			exit(1); //替换失败的退出码设置为1
		}
		//shell
		int status = 0;
		pid_t ret = waitpid(id, &status, 0); //shell等待child退出
		if (ret > 0){
			printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出码
		}
	}
	return 0;
}

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-12 11:16:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-12 11:16:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-12 11:16:01       87 阅读
  4. Python语言-面向对象

    2024-04-12 11:16:01       96 阅读

热门阅读

  1. 总结ES~~~

    2024-04-12 11:16:01       138 阅读
  2. ChatGPT 写手技巧大揭秘:打造高质量论文新策略

    2024-04-12 11:16:01       43 阅读
  3. 【架构-10】DDOS(分布式拒绝服务攻击)

    2024-04-12 11:16:01       39 阅读
  4. node.js 常用命令

    2024-04-12 11:16:01       36 阅读
  5. 这特效确定不学学

    2024-04-12 11:16:01       139 阅读
  6. 机器学习_XGBoost模型_用C++推理示例Demo

    2024-04-12 11:16:01       190 阅读
  7. intellij idea的快速配置详细使用

    2024-04-12 11:16:01       43 阅读
  8. 两种lca模版

    2024-04-12 11:16:01       40 阅读