linux高级编程笔记(三)——进程

目录

一、cpu/mmu

二、PCB

三、环境变量

1 常见的环境变量

1.1 PATH

1.2 SHELL

1.3 LANG

1.4 HOME

2 getenv/setenv/unset函数

四、进程管理

1 fork函数

2 exec函数族

3 回收子进程

孤儿进程

僵尸进程

wait/waitpid函数


一、cpu/mmu

虚拟地址:可用空间有4G,MMU将虚拟地址映射到屋里内存中,即使运行的两个相同的程序,他们非内核区也是在屋里内存中不同的位置,但是,所有进程的内核空间在物理内存中只有一份拷贝

二、PCB

PCB是一个名为task_struct的结构体,它包含如下成员:

三、环境变量

1)字符串(本质)2)统一的格式:变量名=值(:值) 3)用来描述进程环境信息

使用形式:与命令行参数类似

加载位置:与命令行参数类似。位于用户区,高于stack

引入环境变量表:需要声明环境变量。extern char ** environ;

1 常见的环境变量

1.1 PATH

记录可执行程序的路径

1.2 SHELL

记录当前使用命令解析器

1.3 LANG

记录当前的语言

1.4 HOME

当前用户的home目录

2 getenv/setenv/unset函数

char * getenv(const char *name);

描述:

获取环境变量

返回值:

成功返回name对应的环境变量列表

失败返回NULL

int setenv(const char *name,const char *value,int overwrite);

描述:

如果name存在且override为0,那么value将不会更改name对应的值,否则,会覆盖原始环境变量的值

返回值:

成功返回0

失败返回-1,同时设置对应的errno

int unsetenv(const char *name);

描述:

删除环境变量name

返回值:

成功返回0

失败返回-1,同时设置对应的errno

代码示例:

#include <stdlib.h>//getenv/setenv/unsetenv
#include <stdio.h>

int main(int argc,char *argv[])
{
	printf("getenv:%s\n",argv[1]);
	char * value = getenv(argv[1]);

	printf("%s=%s\n",argv[1],value);
	printf("setenv:%s=%s\n",argv[1],argv[2]);
	int ret = setenv(argv[1],argv[2],1);

	printf("ret=%d\n",ret);
	
	printf("unsetenv:%s=%s\n",argv[1],argv[2]);
	ret = unsetenv(argv[1]);
	
	printf("ret=%d\n",ret);
	
	printf("getenv:%s\n",argv[1]);
	value = getenv(argv[1]);
	printf("%s=%s\n",argv[1],value);
	
}

四、进程管理

1 fork函数

pid_t fork(void);

描述:

创建一个子进程

返回值:

失败在父进程返回-1,并设置对应的errno

成功时有如下两种情况

1)在父进程中返回子进程pid(非负整数)

2)在子进程中返回0

代码示例

#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//peror

int main(int argc, char * argv[])
{
	pid_t pid;
	
	pid = fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid == 0)
	{
		printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
	}
	else
	{
		printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
	}

	return 0;
}

其中,getpid()为获取当前进程pid,getppid()为获取当前父进程pid;ps:父进程的父进程pid为当前的shell的pid。

父子进程相同的:

.data .text 堆 栈 环境变量 全局变量 宿主目录位置 进程工作目录 信号处理方式

父子不同:

进程id 返回值 各自的父进程 进程创建时间 闹钟 未决信号集

父子进程共享:

读共享,写独占

文件描述符 mmap映射区

特别的,fork之后父子进程执行的先后顺序不能确定

gdb调试

set follow-fork-mode parent

set follow-fork-mode child

2 exec函数族

 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[]);

描述:

exec族函数用来创建一个新的子进程,该进程会覆盖掉原来所有的数据。

返回:

只有失败的时候才会返回-1,并设置相应的errno,成功的时候直接执行新的子进程,因此,在程序异常处理的时候只需要在exec族函数下面加异常处理逻辑

代码示例:

#include <unistd.h>//exec族
#include <fcntl.h>//flags
#include <stdlib.h>//perror
#include <stdio.h>

int main(int argc,char * argv[])
{
    int fd;
    
    fd = open([argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);
    
    if(fd < 0)
    {
        perror("open error");
        exit(1);
    }

    dup2(fd,STDOUT_FILENO);

    execlp("ps","ps","ax",NULL);
    perror("execlp error");

    return 0;
}

exec函数族中的后缀含义:

l(list) 命令行参数列表

p(path)搜索file时使用path变量

v(vector)使用命令行参数数组

e(environment)使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

3 回收子进程

孤儿进程

父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init,进程,称为init,进程领养孤儿进程。

代码示例:

#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//perror

int main(int argc, char * argv[])
{
	pid_t pid;
	
	pid = fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid == 0)
	{
		printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
        sleep(3);
		printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());

	}
	else
	{
		printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
        sleep(1);
	}

	return 0;
}

尝试在终端运行,结果如下图

我们发现,当父进程推出后,子进程的父进程pid已经由原来的4851变成了2219。我们继续在终端输入ps aux | grep 2219,发现该进程为/sbin/upstart。该进程为linux图形界面下一个专门回收孤儿进程的进程,它是init进程的子进程,如果在字符终端中,则只会由init进程回收子进程。

1

僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。为了放大效果,我们将之前的代码修改为如下结果。父进程在死循环中打印输出,并且不回收子进程,而子进程打印十次之后就退出进程。

#include <stdio.h>
#include <unistd.h>//fork
#include <stdlib.h>//peror

int main(int argc, char * argv[])
{
	pid_t pid;
	
	pid = fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid == 0)
	{
		int i=0;
		for(;i<10;i++)
		{
			printf("I'm child,pid=%u,parrent pid=%u\n",getpid(),getppid());
			sleep(1);
		}
	}
	else
	{
		while(1)
		{
			printf("I'm parent,pid=%u,child pid=%u\n",getpid(),pid);
        		sleep(1);
		}
	}

	return 0;
}

我们在终端中运行该程序,待子进程退出之后,在另一个终端输入ps aux,结果如下。可以看到,最下有两个进程一个,进程id分别为6635(父进程),6636(子进程)。其中子进程使用"[]"包裹并用<defunct>,表示进程死亡但是未被回收(僵尸进程)。



特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的而僵尸进程已经终止。
思考:用什么办法可清除掉僵尸进程呢?

wait/waitpid函数

#include <sys/wait.h>

pid_t wait(int * status); 

pid_t waitpid(pid_t pid,int *status,int options);

参数:

1)pid

>0想要回收的pid

-1回收任意子进程

0回收当前调用waitpid的一个组

<-1回收进程组内的所有进程

ps:wait(NULL)与waitpid(-1,NULL,0)功能一样 

2)status

传出参数,获取子进程的退出状态(return的返回值,信号的值)

ps:status可以使用多种宏定义来判断到底是哪种类型的退出状态

WIFEXITED(status)如果子进程正常结束则为非0 值。

WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。


WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。


WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。

3)option

设置waitpid是阻塞还是非阻塞,0表示阻塞,WNOHANG表示非阻塞示例代码:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    }
    if (pid == 0) {
        int i;
        for (i = 3; i > 0; i--) {
            printf("This is the child, pid=%u\n",getpid());
            sleep(1);
        }
        exit(3);
    } 
    else 
    {
        int stat_val;
        waitpid(pid, &stat_val, 0);
        if (WIFEXITED(stat_val))
        {
            printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
        }
     
        else if (WIFSIGNALED(stat_val))
        {
            printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
        }
        
    }
    return 0;
}

运行该程序后,执行结果如下所示。由于子进程在返回时exit中传入3,因此通过WIFEXITED宏定义可以捕获到正常退出的状态,并且可以用WEXITSTATUS得到对应的值。

为了方便操作,我们将子进程的循环修改为死循环,再次运行该程序,我们在子进程退出之前,另起一个终端,发送sudo kill -9 11014,可以发现子进程因为信号而结束了进程,信号值为kill后的参数

内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、该进程使用的CPU时间总量。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值