Linux编程-进程的执行和退出

fork()复制父进程,生成一个几乎一模一样的子进程 ; 单纯 fork 出来的子进程,代码、数据和父进程完全相同,只能执行父进程现存代码。 exec 系列函数的作用:把当前进程彻底替换成全新程序

exec 一族(execl/execlp/execv/execvp 等): 不创建新进程!在当前 PID 内部,覆盖整个地址空间,用新进程的镜像 替换 原有进程的镜像

  1. 丢弃原来的代码段、数据、堆

  2. 加载磁盘上指定的可执行文件

  3. 从新程序入口点开始运行

    进程 = [text|data|bss|堆栈] + pcb
    ----镜像-----------

    int exec l(const char *path, const char *arg, ...);
    int exec v(const char *path, char *const argv[]);

    @path ---表示要执行的文件的 路径 --- 要求路径名中 要包含可执行文件名
    @arg ---表示的是可执行文件的名字
    @... ---可变参数 注意: 最终以NULL作为结尾
    这块往往给的是,可执行文件需要用到的一些参数

execl与execv的区别:

复制代码
list    --- 逐个罗列,从第二个参数开始,逐个罗列
vector  --- 表示 从第二个参数开始 组织成 数组的形式
表示的是 从第二个参数开始 参数的给定方式 

使用execl查看根目录下的文件:

复制代码
execl("/bin/ls","ls","-l","/",NULL);

第一个参数,为可执行文件的路径,也就是ls命令的路径,为/bin/ls。第二个参数就是可执行文件的名字,就是ls本身,之后为可变长的执行命令参数,-l为查看全部,/为根目录,最后以NULL进行结尾

而execl的使用方法,就是将第二个参数,包含需要执行的文件的名字、执行该文件需要的参数,全部打包成一个指针类型的数组,存入其中,然后将字符型指针数组的首地址,传给execl即可

复制代码
	char *const args[] = {"ls","-l","/",NULL};
	execv("/bin/ls",args);

执行效果与execl相同,因为我测试的时候并没有改变执行的命令,只是改变了执行方式

which 命令

which:查找可执行程序 在磁盘上的真实路径,顺着 PATH 环境变量搜索,只找二进制命令,不找内置 shell 命令、alias 别名。

基础用法;

复制代码
which ls

execlp与execvp的使用

带P --- PATH

表示 寻找可执行文件,到系统的环境变量PATH中寻找

此时不需要指定路径

复制代码
  @file参数 --只需要指定可执行文件的名字即可
int execlp ( const char *file, const char *arg, ...);
int execvp ( const char *file, char *const argv[]);

Linux 的命令分两大类:外部命令(磁盘上真实可执行文件)Shell 内置命令

只有外部命令,磁盘上存在独立可执行程序,才会被 PATH 搜索,which /execvp 能找到并执行。 ❌ shell 内置命令、别名 alias、shell 函数,不在磁盘,PATH 找不到,which查不到,execvp也不能直接执行。

一、属于外部命令(PATH 可搜索)

这些命令磁盘上有二进制文件,which xxx 可以打出路径,execvp("命令名",args) 可以直接跑。 示例:

复制代码
ls、cat、cp、mv、rm、mkdir、rmdir、gcc、g++、python3、ping、ps、top、
grep、find、chmod、chown、tar、zip、unzip、whoami、hostname、date、
wc、sort、awk、sed、less、more

存放位置常见目录(PATH 里面的目录)

  1. /usr/bin 绝大多数用户命令
  2. /bin 系统基础命令
  3. /usr/sbin/sbin 管理员命令

二、Shell 内置命令(PATH找不到,execvp 不能直接调用)

这些命令没有磁盘可执行文件,是 bash 程序内部实现,execvp 执行会报找不到文件。 常见内置:

复制代码
cd、exit、pwd、umask、echo(部分版本)、history、jobs、
fg、bg、set、unset、source(. )

使用execlp执行cp命令

复制代码
execlp("cp","cp","execl.c","5.txt",NULL);

execvp的使用方法与execv类似,就是将第二参数中的命令参数放入数组,使用数组传参

复制代码
	char *const argx[] = {"cp","execl.c","5.txt",NULL};
//	execlp("cp","cp","execl.c","5.txt",NULL);
	execvp("cp",argx);

最后两个为execvpe和execle,带e --- enviroment ---环境变量

如果要执行的文件需要使用到环境变量则,可以通过带e的这两个函数传递环境变量个可执行文件

复制代码
int exec l e(const char *path, const char *arg,..., char * const envp[]);
int exec vp e(const char *file, char *const argv[], char *const envp[]);

使用execvpe打印环境变量:

复制代码
char * const envp[] = {"USR=linux","PASSWD=123456",NULL};
execvpe("env",args,envp);	

分割字符串函数:strtok

复制代码
char *strtok(char *str, const char *delim);

字符串分割,按分隔符delim把字符串切分成一个个子串

  • 参数
    1. str:待切割的字符串。第一次调用传原字符串;后续调用传 NULL
    2. delim:分隔符集合,可以是多个分隔字符
  • 返回值:返回分割出来的子串指针;分割完毕返回NULL
  • strtok 会直接修改原字符串 ,把分隔符位置改写为'\0',原字符串必须可写,不能传字符串字面量"abc,def"(只读段会崩溃)。

示例:分割操作完成后buf中的全部逗号都被替换成了'\0'的字符串分隔符

复制代码
#include <stdio.h>
#include <string.h>

int main(void)
{
    char buf[] = "apple,banana;orange grape";
    char *p;

    // 第一次传入原buf
    p = strtok(buf, ",; ");
    while(p != NULL)
    {
        printf("%s\n", p);
        // 后续全部传NULL
        p = strtok(NULL, ",; ");
    }
    return 0;
}

进程终止的8种情况。

1、main函数执行完毕return返回值

2、exit执行结束,清理io库的所有流文件,注册清理函数

3、_exit执行,直接关闭所有文件流,不执行清理。

4、主线线程退出,类似main函数return结束

5、主线程调用pthread_exit结束,类似kill命令中止进程

异常中止:

6、调用abort()结束

7、接收到kill发送的结束信号,如kill -pid -9;

8、最后一个线程被pthread_cancel取消中止

进程结束:

1、正常rerun结束返回

main函数完成、exit返回、_exit返回、主线程退出、主线程调用了pthread_cancel

2、异常结束接受了外部信号

abort调用结束、接收kill信号结束

退出函数exit与_exit

用户调用函数exit原型示例

复制代码
void exit(int status) 
	exit(1);
	功能:
		让进程退出,并刷新缓存区
	参数:
		status:进程退出的状态
	返回值:
		缺省

	EXIT_SUCCESS	0
	EXIT_FAILURE	1

exit的执行流程:exit -> 刷新缓存区 -> atexit注册的退出函数 -> _exit

系统调用函数_exit:

复制代码
void _exit(int status);
	功能:
		让进程退出,不刷新缓存区
	参数:
		status:进程退出状态
	返回值:
		缺省

回调函数atexit

复制代码
int atexit(void (*function)(void));
	功能:
		注册进程退出前执行的函数
	参数:
		function:函数指针
			指向void返回值void参数的函数指针
	返回值:
		成功返回0
		失败返回非0

	当程序调用exit或者由main函数执行return时,所有用atexit
	注册的退出函数,将会由注册时顺序倒序被调用

注意:
   a. 是exit函数调用时,会调atexit注册的函数 
      _exit函数调用时,不会调到atexit注册的函数
   b. atexit 程序正常结束
      (1).main 返回 //exit 
      (2).exit()  	  
   c.atexit函数 可以多次注册 
   d.最后"退出清理函数"的调用顺序,与注册顺序相反。

回收子进程wait与waitpid

阻塞式回收wait

复制代码
	pid_t wait(int *status);//wait 来获取到 
	功能:
	     该函数可以阻塞等待任意子进程退出
		  并回收该进程的资源和获取退出状态值。
		  一般用于父进程回收子进程状态。

	参数:
	      status 进程退出时候的状态
		  如果不关心其退出状态一般用NULL表示
		 // 如果要回收进程退出状态,则用WEXITSTATUS回收。
	返回值:
	   成功 回收的子进程pid
	   失败 -1

解析进程返回的状态值:

说明 使用前提
WIFEXITED(wstatus) 子进程正常退出(exit/_exit/main return) 返回非 0 代表正常结束
WEXITSTATUS(wstatus) 拿到子进程退出码 (exit (code) /return code) 只有 WIFEXITED 为真才能调用
WIFSIGNALED(wstatus) 子进程被信号杀死(段错误、kill 等) 返回非 0 代表被信号终止
WTERMSIG(wstatus) 获取杀死进程的信号编号 只有 WIFSIGNALED 为真才能调用
WCOREDUMP(wstatus) 是否生成 core dump 核心转储文件 配合 WIFSIGNALED,部分平台支持
WIFSTOPPED(wstatus) 子进程被暂停(SIGSTOP) 开启 WUNTRACED 选项才有效
WSTOPSIG(wstatus) 导致暂停的信号号 WIFSTOPPED 为真使用

WEXITSTATUS 只能在正常退出时用;如果进程是被信号干掉,调用它结果无意义。

可调式回收 pid_t waitpid

复制代码
@参数pid的值:
     <-1 回收指定进程组内的任意子进程
	 -1 回收任意子进程,组内外
	  0 回收和当前调用waitpid一个组的所有子进程,组内
	 >0 回收指定ID的子进程
参数:
	 status 子进程退出时候的状态,
	  		如果不关注退出状态用NULL;
     options 选项:
	  			0       表示回收过程会阻塞等待
				WNOHANG 表示非阻塞模式回收资源。
				
返回值:
	    成功 返回接收资源的子进程pid
		    非阻塞是 返回值为 0 表示 没有任何子进程状态改变 
		失败  -1 
相关推荐
姚不倒11 分钟前
etcd 学习系列(二):集群架构 —— 3 节点是如何工作的
运维·架构·etcd
pt10431 小时前
网络自动化Python课程:Cisco PyATS网络自动化测试框架
运维·自动化
Johny_Zhao2 小时前
网络安全等级保护测评实施方案
linux·网络·人工智能·网络安全·信息安全·云计算·等保测评·系统运维·itsm
小袁拒绝摆烂2 小时前
Jenkins部署经验
运维·jenkins
落羽的落羽2 小时前
【AI】快速理解AI应用的相关名词概念
linux·c++·人工智能·python·计算机网络·算法
刚入门的大一新生3 小时前
Linux-进程控制
linux·运维·服务器·c++
上火的金鱼妹3 小时前
K8S基础组件作用和关系整理
linux·运维·服务器·kubernetes
Vcaker3 小时前
Linux学习25-harbor私有仓库部署
linux·运维·学习
刃神太酷啦3 小时前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap
编程的一拳超人3 小时前
DeepSeek Harness Linux/macOS/Windows 本地下载、启动与模型配置指南
linux·windows·macos·deepseek