文章目录
- 1.进程终止
- 1.1.理解C/C++中main函数的返回值
- 1.2.进程终止的方式
- 2.进程等待
- 2.1.进程等待必要性
- 2.2.进程等待系统调用
- 2.3.获取子进程status
- 3.进程替换
- 3.1.进程程序替换引出
- 3.2.如何进行进程的替换
- 3.3.C/C++调用python
博客的完整代码连接: gitee
1.进程终止
Linux中进程创建会创建task_struct进程控制块、建立映射关系、加载"数据"(代码和数据)等一些列准备工作,反之进程终止操作系统会释放的对应的内核数据结构。简单的理解,创建进程是向操作系统申请资源,因为进程是系统资源分配的单位,而终止进程就是释放资源。
1.1.理解C/C++中main函数的返回值
在语言学习的阶段,main函数的固定结构都是返回值为0,为什么呢?非0行不行呢?
#incldue<stdio.h>int main()
{return 0;
}
返回值为0表示成功,非0表示运行结果不正确,非0有无数个可以标识不同的错误原因。而返回的意义是返回个上一级的父进程,用来判断这个程序的运行结构,当然如果不关心返回值这个return返回值可以忽略,甚至return也可以不写。
可以通过接口strerror
查看系统定义的错误码对应的错误信息,当然错误码也是可以自定义的。
#include <stdio.h>
#include <string.h>int main()
{for(int number = 0;number < 150;number++){printf("the number is %d : strerrno %s\n",number,strerror(number));}return 0;
}
查看上一次进程的退出码:
echo $?
1.2.进程终止的方式
进程终止的场景: 代码运行完毕,结果正确、代码运行完毕,结果不正确、代码异常终止。
进程终止的方法:
正常终止:a.在main函数中,使用return返回;b.调用C语言体统的函数exit;c.系统调用_exit
异常终止:信号终止如:ctrl + c
exit和_exit的区别
其实exit()的底层是调用了_exit(),C语言对 _exit进一定的封装,如果我们使用printf打印函数,但是我们自己并没有主动的去刷新缓冲区,那么使用exit()函数来退出程序,会帮我们把缓冲区的数据刷新。
void test2()
{ // 注意不用\n行刷新printf("hello world");exit(0);
}void test3()
{// 注意不用\n行刷新printf("hello world");_exit(0);
}
分别调用,运行不同的程序,查看两个函数的输出。test2
输出"hello world",test3
不输出!
2.进程等待
2.1.进程等待必要性
原因:1.子进程比父进程先退出,父进程没有等待子进程,那么子进程会处于僵尸状态,如果子进程一直处于僵尸状态那么对应的内核数据结构得不到释放,就是占用内存造成,内存泄漏。其实,当父进程退出了,那么系统就会领养将子进程并将其释放(所以malloc没有free也是可以的,进程退出就释放了,但不要这么干)。但是,一般而言,我们创建的父进程长时间不会退出的,比如一个应用程序,是当用户点击退出的时候才会退出,在服务器端,一个服务器启动之后是不会退出的,要是发生内存泄漏就可以大事。 2.进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程 3.父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出
所以父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息,查看子进程完成父进程交给得任务得如何。
2.2.进程等待系统调用
wait系统调用:
pid_t wait(int *wstatus)
实验代码,创建子进程,父进程阻塞等待子进程
int WaitUse()
{pid_t pid = fork();if(pid < 0) {perror("fork process fail");return 1;}else if(pid == 0){for(int i = 0;i < 5;i++){printf("I am child pid = %d : ppid = %d\n",getpid(),getppid());sleep(1);}}else{pid_t wati_pid = wait(NULL); // 阻塞式等待printf("the wait no of return is %d\n",pid);printf("the father wait child success pid = %d : ppid = %d\n",getpid(),getppid());}return 0;
}
wait_pid系统调用:
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。
status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:可以设置阻塞和非阻塞式的等待,默认是0,阻塞式等待;设置WNOHANG是非阻塞。WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
实验代码
int WaitPidUse()
{pid_t pid = fork();if (pid < 0){perror("fork process fail");return 1;}else if (pid == 0){for (int i = 0; i < 5; i++){printf("I am child pid = %d : ppid = %d\n", getpid(), getppid());sleep(1);}}else{int status = 0;pid_t ret = waitpid(pid, &status, 0);if (ret > 0){int status = 0;pid_t ret = waitpid(pid, &status, 0); // 阻塞式等待printf("this is test for wait\n");if (WIFEXITED(status) && ret == pid){printf("wait child success, child return code is :%d.\n", WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}}return 0;
}
2.3.获取子进程status
当父进程阻塞式等待:wait和waitpid是等效的(waitpid(-1,&status,0) == wait(&status))
退出码和信号等信息都是通过status输出型变量来获取信息的,怎么获取?
3.进程替换
3.1.进程程序替换引出
当我们执行( . /可执行程序)的时候,系统会帮我们创建对应的内核结构和加载对应的可执行程序到内存当中。当我们fork创建子进程的时候并不会加载一份新的可执行程序到内存,而是公用一份父进程的代码。
如果我创建的子进程不想执行父进程中的一个代码片段,而是让我们创建的子进程去执行另外的进程,给怎么做?进程替换
3.2.如何进行进程的替换
替换函数其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>`
//常用
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[]);
函数说明:这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值
命令理解记忆:
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。
#include <stdio.h>
#include <iostream>
#include <unistd.h>using namespace std;int main(int argc,char* argv[],int env[]) // 注意对号入座
{//带不带(char*)没有问题,为了解除警告char *const _rgv[] = {(char*)"ps", "-ef", NULL};char *const _nvp[] = {"PATH=/bin:/usr/bin", "TERM=", NULL};execl("/bin/ps", "ps", "-ef", NULL);// 带p的,可以使用环境变量PATH,无需写全路径execlp("ps", "ps", "-ef", NULL);// int main(char* env[])//这样单独使用 execle函数接口给定环境变量是不行的!// 带e的,需要自己组装环境变量execle("ps", "ps", "-ef", NULL, _nvp);execv("/bin/ps", _rgv);// 带p的,可以使用环境变量PATH,无需写全路径execvp("ps", _rgv);// 带e的,需要自己组装环境变量execve("/bin/ps", _rgv, _nvp);//系统调用execvp("ls",_rgv);exit(0);
}
3.3.C/C++调用python
写一个python脚本模拟实现ll指令,chmod +x python文件,给python添加可执行权限,直接可以执行python脚本
import os
import sys
import stat
import pwd
import grp
import datetimedef get_file_permission(mode):permission = ''permission += 'd' if stat.S_ISDIR(mode) else '-'permission += 'r' if mode & stat.S_IRUSR else '-'permission += 'w' if mode & stat.S_IWUSR else '-'permission += 'x' if mode & stat.S_IXUSR else '-'permission += 'r' if mode & stat.S_IRGRP else '-'permission += 'w' if mode & stat.S_IWGRP else '-'permission += 'x' if mode & stat.S_IXGRP else '-'permission += 'r' if mode & stat.S_IROTH else '-'permission += 'w' if mode & stat.S_IWOTH else '-'permission += 'x' if mode & stat.S_IXOTH else '-'return permissiondef get_file_owner_and_group(uid, gid):try:owner = pwd.getpwuid(uid).pw_nameexcept KeyError:owner = str(uid)try:group = grp.getgrgid(gid).gr_nameexcept KeyError:group = str(gid)return owner, groupdef ll(path='.'):for filename in os.listdir(path):full_path = os.path.join(path, filename)stat_info = os.stat(full_path)mode = stat_info.st_modenlink = stat_info.st_nlinkuid = stat_info.st_uidgid = stat_info.st_gidsize = stat_info.st_sizeatime = datetime.datetime.fromtimestamp(stat_info.st_atime).strftime('%Y-%m-%d %H:%M:%S')mtime = datetime.datetime.fromtimestamp(stat_info.st_mtime).strftime('%Y-%m-%d %H:%M:%S')ctime = datetime.datetime.fromtimestamp(stat_info.st_ctime).strftime('%Y-%m-%d %H:%M:%S')permission = get_file_permission(mode)owner, group = get_file_owner_and_group(uid, gid)print(f'{permission} {nlink:2d} {owner:8s} {group:8s} {size:8d} {atime} {filename}')if __name__ == "__main__":if len(sys.argv) > 1:ll(sys.argv[1])else:ll()
程序替换代码:
int main() // 注意对号入座
{printf("进程替换调用之前!\n");execlp("python3"/*(解释器的位置)*/,"python","test.py",NULL);printf("进程替换调用之后!\n"); // 输出不了,程序替换了return 0;
}