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 
相关推荐
lengjingzju1 小时前
编译与调试完全指南—第5章 GDB调试详解
linux
艾伦_耶格宇1 小时前
【ELK】-5 kibana和filebeat
linux·运维·ubuntu·elk
复园电子2 小时前
USB服务器架构设计分析:多设备管理、权限控制与安全传输机制
运维·服务器·安全
DevHub2 小时前
电视盒子刷 Armbian 教程:30 元旧盒子变 NAS 跑 Docker,200+ 机型可刷
linux·嵌入式硬件·docker·容器·开源·电视盒子
Doraemomo4 小时前
Linux编程-线程
linux·运维·服务器
Linux-lucky10 小时前
21-Linux学习之旅之HTTPS和安全加固
linux·运维·学习·ubuntu·https
清水白石00811 小时前
Python 死锁排查全攻略:从线程卡死到锁依赖定位与工程化修复
linux·网络·python
ao-weilai12 小时前
Linux基础:Ext系列文件系统
linux·运维·服务器
qq_3494479512 小时前
Linux系统,安装git,从使用git下载仓库(GitHub, Gitee, GitLab 等),并且使用ssh密钥,可以直接执行git pull
linux·git·ssh