linux---进程控制

2023-05-16

进程控制

fork函数
创建一个子进程。
pid_t fork(void); 失败返回-1;成功返回:① 父进程返回子进程的ID(非负) ②子进程返回 0
pid_t vfork(void);同样时创建一个子进程,但是他是与父进程公用一块虚拟空间,子进程先运行,等到子进程exit(0)的时候父进程才运行。
pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
循环创建n个子进程
一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎样实现呢?
简单想,for(i = 0; i < n; i++) { fork() } 即可。但这样创建的是N个子进程吗?
在这里插入图片描述

  • 循环创建N个子进程

从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n)-1个子进程,而不是N的子进程。需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break;才正确。
练习:通过命令行参数指定创建进程的个数,每个进程休眠1S打印自己是第几个被创建的进程。如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程”。 //fork1.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(void)
{
    int i;
    pid_t pid;
    printf("xxxxxxxxxxx\n");

for (i = 0; i < 5; i++) {
    pid = fork();
    if (pid == 0) {
        break;
    }
}

if (i < 5) {

    sleep(i);
    printf("I'am %d child , pid = %u\n", i+1, getpid());

} else  {
    sleep(i);
    printf("I'm parent\n");
}

return 0;
}

通过该练习掌握框架:循环创建n个子进程,使用循环因子i对创建的子进程加以区分。
getpid函数
获取当前进程ID
pid_t getpid(void);
getppid函数
获取当前进程的父进程ID
pid_t getppid(void);
区分一个函数是“系统函数”还是“库函数”依据:
② 是否访问内核数据结构
② 是否访问外部硬件资源 二者有任一 → 系统函数;二者均无 → 库函数
getuid函数(了解)
获取当前进程实际用户ID
uid_t getuid(void);
获取当前进程有效用户ID
uid_t geteuid(void);
getgid函数(了解)
获取当前进程使用用户组ID
gid_t getgid(void);
获取当前进程有效用户组ID
gid_t getegid(void);
进程共享
父子进程之间在fork后。有哪些相同,那些相异之处呢?
刚fork之后:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式…
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
练习:编写程序测试,父子进程是否共享全局变。

//fork_shared.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int a = 100;            //.data 

int main(void)
{
	pid_t pid;
	pid = fork();

	if(pid == 0){	//son
		a = 2000;
		printf("child, a = %d\n", a);
	} else {
		sleep(1);	//保证son先运行
		printf("parent, a = %d\n", a);
	}

	return 0;
}

重点注意!躲避父子进程共享全局变量的知识误区!
【重点】:父子进程共享:1. 文件描述符(打开文件的结构体) 2. mmap建立的映射区 (进程间通信详解)

特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。

exec函数族

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其实有六种以exec开头的函数,统称exec函数:
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[]);
execlp函数
加载一个进程,借助PATH环境变量
int execlp(const char *file, const char *arg, …); 成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
execl函数
加载一个进程, 通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, …); 成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp(“ls”, “ls”, “-l”, “-F”, NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", “ls”, “-l”, “-F”, NULL); 使用参数1给出的绝对路径搜索。
execvp函数(了解)
加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式: ①… ② argv[] (main函数也是变参函数,形式上等同于 int main(int argc, char *argv0, …))
变参终止条件:① NULL结尾 ② 固参指定
execvp与execlp参数形式不同,原理一致。
练习:将当前系统中的进程信息,打印到文件中。

//exec_ps.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int fd;

	fd = open("ps.out", O_WRONLY|O_CREAT|O_TRUNC, 0644);
	if(fd < 0){
		perror("open ps.out error");
		exit(1);
	}
	dup2(fd, STDOUT_FILENO);

	execlp("ps", "ps", "ax", NULL);
	//close(fd);

	return 0;
}

exec函数族一般规律(了解)
exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
l (list) 命令行参数列表
p (path) 搜素file时使用path变量
v (vector) 使用命令行参数数组
e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execve在man手册第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux---进程控制 的相关文章

随机推荐

  • shell脚本基础

    摘自 xff1a 一篇教会你写90 的shell脚本 1 注释 xff1a 单行注释 xff1a 多行注释 xff1a 注意第一种注释方法的 34 后面有一个空格 xff09 39 多行注释内容 39 lt lt block 多行注释内容
  • RT-thread 中CAN总线的应用

    准备 xff1a RT thread Studio 2 2 5 CubeMX 6 6 1 rt thread驱动包 4 0 3 1 新建项目 2 打开CubeMX Settings xff0c 设置CAN 找到CAN1 xff0c 并勾选激
  • Rt-thread的CAN应用2

    1 rtt中使能CAN1 2 CubeMX生成部分代码 xff0c 完成下面操作然后生成MDK ARM项目文件 3 将CubeMX生成的 HAL CAN MspInit 函数 粘贴到drv can c中 并在rt hw can init 函
  • STM32的IIC接口输入输出定义

    IO方向设置 PC11 端口 define SDA IN GPIOC gt CRH amp 61 0XFFFF0FFF GPIOC gt CRH 61 8 lt lt 12 define SDA OUT GPIOC gt CRH amp 6
  • 从头开始用树莓派做一个NAS【最新超详细教程】

    一 概述 众所周知在办公的时候两台电脑之间经常倒数据资料非常麻烦 xff0c 而NAS可以很好的解决这个问题 树莓派搭建NAS方法有很多 xff0c 我们之前也拍过直接用Samba FTP这些来实现NAS功能 xff0c 但是这些需要你会在
  • 串口发送通信---UART发送---STM32F4实现

    串口发送程序配置过程 xff08 HAL库 xff09 初始化串口相关参数 xff0c 使能串口 HAL StatusTypeDef span class token function HAL UART Init span span cla
  • 基于UDP的文件传输VS编程

    要求 xff1a 实现基于UDP的文件传输 xff0c 客户端发送文件 xff0c 服务端接受文件 服务端实现 xff1a include 34 stdafx h 34 include 34 iostream 34 include 34 a
  • 成功解决VSCODE运行C++的报错问题g++

    成功解决VSCODE运行C 43 43 的报错问题g 43 43 解决方案调试 很久写C 43 43 了 xff0c 直接打开C 43 43 写代码测试一下 xff0c 报错 xff0c 真一脸懵逼 捯饬很久 xff0c 解决了问题 解决方
  • linux下的环境变量

    环境变量 环境变量 xff0c 是指在操作系统中用来指定操作系统运行环境的一些参数 通常具备以下特征 xff1a 字符串 本质 有统一的格式 xff1a 名 61 值 值 值用来描述进程环境信息 存储形式 xff1a 与命令行参数类似 ch
  • 【004 关键字】extern “C“的作用是什么?

    一 extern 34 C 34 的作用 extern 34 C 34 关键字常用于C 43 43 和C混合编程中 xff0c 用于指定函数或变量采用C语言的命名和调用约定 加上extern 34 C 34 后 xff0c 会指示编译器这部
  • C++模板编程--学习C++类库的编程基础

    目录 一 函数模板二 类模板三 实现C 43 43 STL向量容器vector代码四 理解容器空间配置器allocator的重要性 一 函数模板 1 模板的意义 xff1a 对类型也可以进行参数化了 2 函数模板 lt 61 是不进行编译的
  • cmake 学习使用笔记(二)库的生成与使用

    学习使用cmake 生成及使用库 xff0c 使用cmake 生成 lib 静态库 及动态库 xff08 dll xff09 及linux 下使用的静态库 a 和 动态库 xff08 so 也称为共享库 xff09 目录 使用工具 生成库
  • cmake 学习使用笔记(五)手动编译

    目录 编译cmake 项目 构建 方式一 xff1a 可执行文件生成的在当前目录 方式二 xff1a 可执行文件生成只制定目录 记录一下手动编译 cmake 项目 编译cmake 项目 项目结构 注意 xff1a 必须要有一个 CMakeL
  • 四旋翼无人机学习第5节--STM32最小系统外围电路分析

    文章目录 1 芯片手册分析2 stm32的外部晶振手册分析2 stm32的外部上电复位手册分析3 电源放置方法4 GND放置方法5 其他元器件放置方法6 网络放置方法7 快捷键分享8 原理图绘制成果分享 快速使用芯片的最好的方式 xff0c
  • 现代C++单文件库(header only)

    nlohmann json https github com nlohmann json easyloggingpp https github com amrayn easyloggingpp
  • 一、ROS创建工作空间

    一 ROS创建工作空间 1 什么是工作空间2 创建工作空间3 编译工作空间4 创建功能包5 编译功能包 xff08 重新编译工作空间 xff09 6 设置环境变量7 运行节点 1 什么是工作空间 工作空间 xff08 workspace x
  • 二、ROS集成开发环境

    二 ROS集成开发环境 1 安装terminator2 安装VScode3 在VScode中使用ROS3 1 创建工作空间3 2 启动VScode3 3 配置VScode3 4 创建功能包3 5 编写C 43 43 文件3 6 配置CMak
  • 十一、Ubuntu18.04下VSCode配置C/C++编译环境

    十一 Ubuntu18 04下VSCode配置C C 43 43 编译环境 1 安装VSCode2 安装插件3 创建工程4 总结 1 安装VSCode 这一步就不谈了 2 安装插件 编译一些简单的cpp文件 xff0c 下面几个插件就够用了
  • 十五、Typora官方主题 + 自定义主题

    十五 Typora官方主题 43 自定义主题 1 下载官方主题2 在官方主题的基础上自定义 1 下载官方主题 首先去Typora官网下载自己喜欢的主题 xff1a 官方主题下载 例如我这里选择 Vue 我们只用到里面的两个主题文件vue c
  • linux---进程控制

    进程控制 fork函数 创建一个子进程 pid t fork void 失败返回 1 xff1b 成功返回 xff1a 父进程返回子进程的ID 非负 子进程返回 0 pid t vfork void 同样时创建一个子进程 xff0c 但是他