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 
相关推荐
陈希12313 分钟前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡1 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
@兽血沸腾1 小时前
Windows系统安装Jenkins部署应用至远程Windows服务器操作指南
运维·服务器·jenkins
且从容.4 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道5 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
讯捷蓝达7 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
且从容.8 小时前
ubuntu安装flink
linux·ubuntu·flink
数字新视界9 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落9 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨9 小时前
WSL2的安装与使用
linux·wsl