Linux系统编程【进程控制】(5)

举报
xcc-2022 发表于 2022/10/24 20:47:54 2022/10/24
1.1k+ 0 0
【摘要】 16. 孤儿进程父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。 ...

16. 孤儿进程

父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。

因此孤儿进程并不会有什么危害。

image-20220924100002162

17. 僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。

 pid_t pid = -1;

    //创建一个子进程
    pid = fork();

    //执行子进程
    if(pid == 0)
    {
        for(int i=0;i<5;i++)
        {
        
        printf("子进程偷懒了%d次\n",i);
        sleep(1);
         }
        
        printf("子进程被发现了,先退出了...\n");

        //子进程退出
        exit(0);
    }

    sleep(100);
    printf("父进程偷懒结束 父进程退出...\n");

    return 0;

image-20220924104519253

使用killall a.out杀死

18. 进程替换

概述

在 Windows 平台下,我们可以通过双击运行可执行程序,让这个可执行程序成为一个进程;而在 Linux 平台,我们可以通过 ./ 运行,让一个可执行程序成为一个进程。

但是,如果我们本来就运行着一个程序(进程),我们如何在这个进程内部启动一个外部程序,由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 exec 函数族实现。

exec 函数族,顾名思义,就是一簇函数,在 Linux 中,并不存在 exec() 函数,exec 指的是一组函数,一共有 6 个:

#include <unistd.h>
extern char **environ;

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

int execve(const char *filename, char *const argv[], char *const envp[]);

其中只有 execve() 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

exec 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

进程调用一种 exec 函数时,该进程完全由新程序替换,而新程序则从其 main 函数开始执行。因为调用 exec 并不创建新进程,所以前后的进程 ID (当然还有父进程号、进程组号、当前工作目录……)并未改变。exec 只是用另一个新程序替换了当前进程的正文、数据、堆和栈段(进程替换)。

1527922959390

exec 函数族使用说明

exec 函数族的 6 个函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。

1527923035885

补充说明:

l(list) 参数地址列表,以空指针结尾
v(vector) 存有各参数地址的指针数组的地址
p(path) 按 PATH 环境变量指定的目录搜索可执行文件
e(environment) 存有环境变量字符串地址的指针数组的地址

exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回,而且,exec 函数族下面的代码执行不到。只有调用失败了,它们才会返回 -1,失败后从原程序的调用点接着往下执行。

execlp函数

示例代码:

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

//进程替换
int main()
{

    printf("hello 挨踢程序员\n");
    //arg0 arg1 arg2....argn
    //argo 一般是可执行文件名  argn必须是NULL

    execlp("ls","ls","-l","/home/test",NULL);//等价于ls -l /home/test

    printf("hello world\n");
    return 0;
}

运行结果:

image-20220924111958351

最后一句的“hello world”没有被打印,为什么?画图分析一下:

image-20220924112926590

execl函数

和execlp相似,它们就第一个参数不同,一个是可执行文件名,另一个是执行文件的路径

int execl(const char *path, const char *arg, .../* (char  *) NULL */);

image-20220924113533696

execv/execvp函数

int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

image-20220924115008677

execle/execvpe函数

int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */)
int execvpe(const char *file, char *const argv[], char *const envp[]);

envp:环境变量

用法:

#define _GNU_SOURCE
#include<string.h>
#include<stdio.h>
#include<unistd.h>
char *envp[] = {"ADDR=BEIJING",NULL};
//最后一个参数是环境变量指针数组
execle("/bin/ls","ls","ls","-l","home/test",null,envp);
//第一个参数是可执行文件
//第二个参数是参数列表 指针数组
//第三个参数是环境变量列表 指针数组
execvpe("ls",argv,envp);

运行结果:

image-20220924120027267

19. 源码包安装(扩展)

第一步:生成Makefile和检测当前环境

xcc@machine:~/tools/valgrind-3.13.0 sudo ./configure

第二步: 编译源码 生成可执行文件

xcc@machine:~/tools/valgrind-3.13.0$ sudo make -j4

第三步: 安装

xcc@machine:~/tools/valgrind-3.13.0$ sudo make install

**第四步:**测试

xcc@machine:~/tools/valgrind-3.13.0$ valgrind

valgrind: no program specified

valgrind: Use --help for more information.

20. 作业

1)编写测试程序,测试fork之前打开文件,父子进程之间是否共享文件

提示:

  • 子进程write
  • 父进程read

2)多进程编程

父进程fork三个子进程:

  • 一个调用ps命令
  • 一个调用自定义应用程序
  • 一个调用会出现段错误的程序

父进程回收三个子进程(waitpid),并且打印三个子进程的退出状态

3) 文件操作

统计出指定目录中普通文件的个数.(递归)

4)实现ps aux | grep bash功能

​ 提示:fork(), pipe(), dup2(), execlp()

2017-11-22_175039

21.答案

3) 文件操作

统计出指定目录中普通文件的个数.(递归)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
int get_file_num(char* root)
{
	int total = 0;
	DIR* dir = NULL;
	// 打开目录
	dir = opendir(root);
	// 循环从目录中读文件

	char path[1024];
	// 定义记录xiang指针
	struct dirent* ptr = NULL;
	while( (ptr = readdir(dir)) != NULL)
	{
		// 跳过. he ..
		if(strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
		{
			continue;
		}
		// 判断是不是目录
		if(ptr->d_type == DT_DIR)
		{
			///home/deng/share
			sprintf(path, "%s/%s", root, ptr->d_name);
			// 递归读目录
			total += get_file_num(path);
		}
		// 如果是普通文件
		if(ptr->d_type == DT_REG)
		{
			total ++;
		}
	}
	closedir(dir);
	return total;
}

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		printf("./a.out path");
		exit(1);
	}

	int total = get_file_num(argv[1]);
	printf("%s has regfile number: %d\n", argv[1], total);
	return 0;
}

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。