[C++系列] UNIX高级编程 day09 进程资源回收,环境变量,加载映像,system函数
in C/C++ with 0 comment

[C++系列] UNIX高级编程 day09 进程资源回收,环境变量,加载映像,system函数

in C/C++ with 0 comment

复习:

一,文件锁

二,库函数和系统调用之间的关系
三,文件操作的杂项
四,进程的基础

一,进程资源的回收

在进程中止以后,系统要回收子进程所占用的资源。验证进程的死亡原因。
在进程中父进程负责回收子进程的资源
父进程使用wait(2)家族的函数,回收资源。

#include<sys/types.h>
#include <sys/wait.h>
 pid_t     wait(int *stat_loc);

功能:等待子进程的终止,并且获取子进程的相关信息。
参数:
status:
!NULL。将子进程的退出状态信息保存到status指向到地址空间里。并且可以使用一些宏来检测进程的退出状态。
WIFEXITED(status). 如果子进程正常终止,返回真.
WEXITSTATUS(status)获取子进程的退出状态码,只有在上个宏为真的情况下使用
WIFSUGNALED(status)如果子进程是被信号终止的,返回真
WTERMSIG(status) 返回终止的子进程的信号的编号,只有在上个宏为真的情况下使用。
NULL:
返回值:
成功:返回终止子进程的pid
信号。1~64。! 32 33
阻塞等待子进程的终止。

如何给一个进程发送型号:
kill。-信号编号 pid 进程收到信号以后,默认的处理动作是终止当前进程。

pid_t waitpid(pid_t pid, int *stat_loc, int options);
功能
参数
Pid: 要等待的子进程的id
<-1 等待任意子进程,这些子进程的组id等于pid的绝对值
-1 任意等待子进程
0 等待和当前进程同组的子进程。
>0。 等待的子进程的id就是pid的值
Status:!NULL。将子进程的退出状态信息保存到status指向到地址空间里。并且可以使用一些宏来检测进程的退出状态。
Options:默认情况下,挂起当前进程,等待子进程的终止。但是可以通过设置option参数的值来修改默认的值。
返回值:
-1 错误
返回子进程id,如果WNOHANG指定,没有子进程的退出,返回0.

WNOHANG:如果没有子进程中止,立即返回,返回的是一个错误0

进程组:
一般情况下,创建的子进程和父进程都属于同组的进程。
当然,创建子进以后,也可以将子进程设置为其它进程的子进程。

代码

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include <sys/wait.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 int main(){
  7         pid_t pid;
  8         pid = fork();
  9         int s;
10         if(pid == -1){
11         perror("fork");
12         return -1;
13         }
14         if(pid == 0){
15         printf("this is a child..\n");
16         sleep(5);
17         exit(0);
18         }else{
19         //阻塞等待子进程的结束。
20         wait(&s);
21         if(WIFEXITED(s)){
22                 printf("exit code:%d\n",WEXITSTATUS(s));
23
24         }
25         printf("this is parent..\n");
26         }
        }

信号终止

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include <sys/wait.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 int main(){
  8         pid_t pid;
  9         pid = fork();
10         int s;
11         if(pid == -1){
12         perror("fork");
13         return -1;
14         }
15         if(pid == 0){
16         printf("child pid %d\n",getpid());
17         printf("this is a child..\n");
18         getchar();
19         exit(0);
20         }else{
21         //阻塞等待子进程的结束。
22         wait(&s);
23         if(WIFEXITED(s)){
24                 printf("exit code:%d\n",WEXITSTATUS(s));
25
26         }
27         if(WIFSIGNALED(s)){
28         printf("signal %d \n",WTERMSIG(s));

僵尸进程
在子进程中止的时候,父进程还没有来处理,这时候的子进程处于僵尸状态,称这时候的子进程专业术语为僵尸进程。

#include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int main(){
  5         pid_t pid;
  6         //创建子进程
  7         pid=fork();
  8         if(pid == -1){
  9         perror("fork");
10         return -1;
11         }
12         if(pid== 0){
13                 printf("child pid %d\n",getpid());
14                 exit(0);
15         }else{
16                 printf("parent pid %d\n",getpid());
17         getchar();
18         }
19         return 0;
20 }

举例:

使用waitpid等待子进程结束

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include <sys/wait.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 int main(){
  8         pid_t pid;
  9         pid = fork();
10         int s;
11         if(pid == -1){
12         perror("fork");
13         return -1;
14         }
15         if(pid == 0){
16         printf("child pid %d\n",getpid());
17         printf("this is a child..\n");
18         getchar();
19         exit(0);
20         }else{
21         //阻塞等待子进程的结束。
22         int w = waitpid(-1,&s,WNOHANG);
23         if(w == 0){
24                 printf("no child terminaled..\n");
25                 return 0;
26         }
27         if(WIFEXITED(s)){
28                 printf("exit code:%d\n",WEXITSTATUS(s))
             }
31         if(WIFSIGNALED(s)){
32         printf("signal %d \n",WTERMSIG(s));
33         }
34         printf("this is parent..\n");
35         }

同步异步的问题:
两个任务同步活着异步。两个或多个进程。这些进程互为同步或异步。
同步就是两件事情A或B。只有执行完A的情况下才去执行事情B;
异步是两件事情先执行那一个不确定。
创建了一个子进程的时候,子进程和父进程是异步的。
但是可以在父进程中调用wait(2)。将异步的使用变为同步。

bash。下运行a.out
bash为我们fork了子进程,在子进程中执行新的程序。在子进程中执行a.out的映像。
a.out 是bash的子进程
bash调用wait(2)家族的函数,回收a.out 的资源。
使用命令对环境变量操作

二,环境变量

提供给程序的运行环境。
以上为bash下的环境变量,bash
在bash下,变量有两种。
自定义变量 :只能在当前的bash中起作用。
环境变量:可以被子进程继承,不仅能在当前大bash下起作用,在子进程中也起作用。

在系统中有一个全局变量,记录了进程的环境变量存储区的地址。
Extern char **environ;
使用全局变量environ遍历所有的环境变量

举例

1 #include<stdio.h>
2 #include<unistd.h>
3 int main(){
4         extern char **environ;
5         int i;
6         for(i=0;environ[i]!=NULL;i++){
7                 printf("%s\n",environ[i]);
8                 return 0;
9         }
10 }
//main函数的参数中也有环境变量,父进程的环境变量继承给子进程的栈段中。
Int main(int arg,char *argv[],char *evnp[]){


}

如何在进程中获取环境变量:

Getenv(3)
#include <stdlib.h>
char *   getenv(const char *name);

功能:从环境变量列表中搜索环境变量的值。
参数
name:指定了环境变量的名字。
返回值:
NULL。 没找到
返回环境变量值的地址

举例

获取环境变量的值

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main(){
  4         printf("%s\n",getenv("name"));
  5         return 0;
  6 }
~

设置环境变量的值:
putenv(3)

    #include <stdlib.h>
char *    getenv(const char *name);

功能:改变环境变量的值,如果环境变量不存在,增加环境变量
参数
String:格式如此 name=value
返回值
成功 0
错误 非0

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main(){
  4         putenv("name=drop.org.cn");
  5         printf("%s\n",getenv("name"));
  6         return 0;
  7 }
//这个程序知识改变的当前程序进程的环境变量
//当在bash下 echo $name时,会发现name的值没有改变,原因是bash是本程序的父进程,不会继承

Setenv(2)

#include <stdlib.h>
int     setenv(const char *name, const char *value, int overwrite);

功能:改变或增加环境变量
参数
Name:指定了环境变量的名字
Value:指定了环境变量的值
Overwrite:
如果环境变量存在。这个参数指定为0,代表不改变。如果是非0代表使用value的值更改环境变量的值。

返回值
0 成功
-1 错误,error被设置

unsetenv()

int unsetenv(const char *name);
功能:删除环境变量,如果环境变量不存在,不改变环境变量列表
参数
Name:指定要删除的环境变量
返回值:
0 成功
-1 错误,error被设置

举例

Setenv() unsetenv();使用

#include<stdio.h>
  2 #include<stdlib.h>
  3 int main(){
  4         printf("%s\n",getenv("name"));
  5         putenv("name=drop.org.cn");
  6         printf("%s\n",getenv("name"));
  7         setenv("name","drops",1);
  8         printf("%s\n",getenv("name"));
  9         unsetenv("name");
10         printf("%s\n",getenv("name"));
11         return 0;
12 }
~```

include<stdlib.h>

Int clearenv(void)

**功能**:清除环境变量
**参数**:void
**返回值**:
0        成功
其它        失败



## 三,加载新的映像

什么是映像:    可以理解为可执行的二进制文件。
为了加载新的映像,系统提供了`exec(3)`家族的函数。
`execve(2)`

include <unistd.h>

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

**功能**:执行程序
**参数**:
Filename:指定了可执行文件的名字

Argv[];传递给新进程的参数字符串列表,这个列表的第一个应该包含filename,

Envp[]:传递给新进程的环境变量字符串列表,最后一个成员是NULL

**返回值**:
成功:不返回

返回就错误    -1    errno被设置


## 举例
使用`execve()`加载新的映像

   

1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/types.h>
4 #include<sys/wait.h>
5 #include<unistd.h>
6
7
8 int main(){
9 pid_t pid;
10 char* const env_argv[]={"./renv",NULL};
11 char* const envp[]={"city=guangzhou",NULL};
12 //创建新的进程
13 pid=fork();
14 if(pid == -1){
15 perror("fork");
16 return -1;
17 }
18 if(pid == 0){//子进程
19 execve("./renv",env_argv,envp);
20 perror("execve()");
21 exit(0);
22 }else{//父进程
23 wait(NULL);
24 }
25 return 0;
26 }
~`

有这个系统调用延展的家族

#include <unistd.h>
     extern char **environ;
 int   execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int     execle(const char *path, const char *arg0, ...
         /*, (char *)0, char *const envp[] */);
 int     execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
 int    execv(const char *path, char *const argv[]);
 int     execvp(const char *file, char *const argv[]);
 int    execvP(const char *file, const char *search_path, char *const argv[]);

以上函数族中带有的字母有以下:

l:将传递给新进程的参数一个一个地列出来,列表形式。
V:将传递给新进程的参数作为一个向量表传递。
P: 指定的文件。这个文件到path指定的路径下找。如果不指定这个p需要告诉函数这个可执行文件的路径。
e:是否给新的进程传递环境变量。如果有e代表给新的进程传递环境变量,使用传递的环境变量列表覆盖从父进程继承下来的环境变量列表。

举例

加载新的映像文件

ps -o "pid,ppid,pgrp,comm”

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/types.h>
  4 #include<unistd.h>
  5 #include<sys/wait.h>
  6 int main(){
  7         pid_t pid;
  8         //创建一个子进程
  9         pid = fork();
10         if(pid == -1){
11                 perror("fork");
12                 return -1;
13         }
14         if(pid == 0){//子进程
15                 execlp("ps","ps","-o","pid,ppid,pgrp,comm",NULL);
16                 perror("execlp");
17         }else{
18                 wait(NULL);
19         }
20
21 }
~```

在`bash`启动的a.out   可执行程序,`bash`先调用`fork`创建一个子进程,然后在子进程中使用新的映像替换掉旧的映像`exec(3)`家族函数完成,这样,子进程就执行新的映像
`Exec`家族函数的功能就是替换映像。

在`bash`下执行的`linux`命令也是以上所述。`bash先fork再exec`加载可执行命令的映像。

`bash`下的命令分为两类。一类`内部命令`,一类是`外部命令`。
内部命令实现跟`bash`是一个程序。
而外部命令的实现跟`bash`不是同一个程序
如何查看一个命令是外部命令。
    `type 命令`


## 四,`system(3)`的使用

include <stdlib.h>

int system(const char *command);

**功能**:执行一个shell命令。
**参数**:
command:指定了要执行的shell命令。   /bin/sh -c
**返回值**:
-1     错误
成功        返回command执行的退出状态吗。

shell是一个统称 sh是shell的一种。 bash又是shell的一种。
bash a.out /bin/sh command
bash a.out command

## 举例
`System(3)`的使用
设计一个delay.c     用于延时
在设计一个system.c调用delay.c。利用pstree查看结果

Bash—system—sh——delay
Bash—delay

## 总结:
一,回收子进程的资源      `  wait    wait_pid`
    **僵尸进程**

二,环境变量
三,加载进程的新的映像

    使用新映像替换旧映像。
四,`system`函数使用

## 作业:
编写代码实现自己的shell,编译成可执行文件,为rshell

Responses