【APUE】文件系统 — 进程环境

news2024/10/6 8:32:33

目录

一、再提 main 函数

二、进程的终止 

2.1 正常终止

2.1.1 从 main 函数用 return 返回

2.1.2 主动调用 exit 函数

2.1.3 钩子函数 

2.1.4 调用 _exit 或 _Exit

2.2 异常终止

三、命令行参数的分析

3.1 getopt

3.2 示例 

四、环境变量

4.1 简介

4.2 查看环境变量

4.3 environ

4.4 getenv

4.5 setenv

4.6 putenv

五、C程序的存储空间布局

六、库

6.1 动态库

6.2 静态库

6.3 共享库(手动装载库) 

6.3.1 dlopen

6.3.2 dlclose

6.3.3 dlerror

6.3.4 dlsym

6.3.5  man 手册中的代码示例

七、函数跳转

7.1 setjmp

7.2 longjmp

代码示例 

八、资源的获取及控制 

8.1 ulimit 命令

8.2 getrlimit

8.3 setrlimit


一、再提 main 函数

int main(int argc, char *argv[]) {

}

C 程序总是从 main 函数开始执行,从 main 函数结束执行。即 main 是程序的入口和出口


二、进程的终止 

2.1 正常终止

  1. 从 main 函数用 return返回
  2. 主动调用 exit 函数
  3. 调用 _exit 或 _Exit
  4. 最后一个线程从其启动例程(启动例程说白了就是一个线程本身)返回
  5. 最后一个线程调用了 pthread_exit 

下面介绍其中一部分 


2.1.1 从 main 函数用 return 返回

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("hello\n");
    return 0;    // 正常终止
{

其中,正常终止的语句 return 0 是给父进程看的 

如上所示,在上述情况下,父进程为 shell,也就是说 return 的值给 shell 了,可以在 shell 中通过如下命令显示最后命令的退出状态

echo $?

2.1.2 主动调用 exit 函数

man exit

NAME
       exit - cause normal process termination

SYNOPSIS
       #include <stdlib.h>

       void exit(int status);

DESCRIPTION
       The exit() function causes normal process termination and the value of status & 0377 is returned to the parent.

注意, 调用该函数后,返回给父进程的值只保留 status 的低八位,即返回给父进程的值只可能是 0 到 255

此外,return n 等同于 exit(n) 

2.1.3 钩子函数 

基本概念:当程序从 main 函数中通过 return 或者 exit 正常终止程序时,会被自动调用的函数,有时又称这些函数为退出处理程序

那么,如何能够让一个函数成为钩子函数?

man 3 atxit

#include <stdlib.h>

int atexit(void (*function)(void));

功能:将特定函数注册成为钩子函数

  • function — 指向拟希望注册为钩子函数的函数,注意只有形参为 void 且返回值也为 void 的函数才能被注册为钩子函数 
  • 注册成功返回 0,失败返回非 0

代码示例

#include <stdio.h>
#include <stdlib.h>

// 终止处理程序
static void f1() {
        puts("f1() is working!");
}

// 终止处理程序
static void f2() {
        puts("f2() is working!");
}

// 终止处理程序
static void f3() {
        puts("f3() is working!");
}

int main() {
        puts("Begin");
        // 先注册的后被调用
        // 钩子函数注册的时候并不会执行,当main通过exit或者return终止程序的时候才执行
        // atexit参数用指针来接收,因此需要传入地址,而函数名就是函数的地址
        // 可以重复注册同一函数多次,这样会在程序终止时执行该函数多次
        atexit(f1);
        atexit(f1);
        atexit(f2);
        atexit(f3);

        puts("End");

        exit(0);
}

2.1.4 调用 _exit 或 _Exit

_exit 和 _Exit 完全等价! 功能是立即正常终止程序

这和上面讲的 exit 有什么联系与区别呢? 

exit 是库函数,而 _exit 是系统调用,对 _exit 进行封装从而实现了 exit

也就是说,调用 exit,然后 exit 会调用 _exit. 不过 exit 在调用 _exit 之前还会执行一系列动作:

  1. 调用退出处理程序
  2. 刷新 stdio 流缓冲区

在这之后才会使用由 status 提供的值执行 _exit 系统调用


2.2 异常终止

  1. 调用了 abort,给当前进程发送信号
  2. 接受到一个信号并终止,如 ctrl + C
  3. 最后一个线程收到取消请求,并作出响应

三、命令行参数的分析

C 语言程序主要通过 main 函数的参数来传递命令行参数,其中 argc 表示参数个数(包含程序本身),argv 是保存所有这些参数的 char* 数组

int main(int argc, char * argv[]) {

}

我们可以自己写东东,通过 argc 和 argv 来处理命令行参数,但是 C 标准库提供了一个更丰富的处理命令行参数的方法:getopt

有人说,为什么需要这个方法?原因在于相同功能的等价命令可能有不同的书写形式

        一个典型的 UNIX 命令行有着如下的形式

        选项的形式为连字符(-)紧跟着一个唯一的字符用来标识该选项,以及一个针对该选项
的可选参数。带有一个参数的选项能够以可选的方式在参数和选项之间用空格分开。多个选
项可以在一个单独的连字符后归组在一起,而组中最后一个选项可能会带有一个参数。根据

这些规则,下面这些命令都是等同的

        在上面这些命令中, -l 和 -i 选项没有参数,而 -f 选项将字符串 pattern 当做它的参数,因为许多程序都需要按照上述格式来解析选项,而仅靠手动通过 argc 和 argv 达到这样的效果是很麻烦的。因此引入了 getopt

在介绍 getopt 之前,先介绍一下命令行参数各组成部分的名称 

命令行参数由 Command name,Option,Option argument 以及 Operands 组成

  • Command name — 程序名
  • Operands — 操作对象,又称 nonoption argument
  • Option — 选项,它是用来用来决定程序的行为,而选项参数 Option argument 是选项 Option 所需要的信息


3.1 getopt

man 3 getopt

函数声明: 

#include <unistd.h>

int getopt(int argc, char * const argv[], const char *optstring);
  • argc — 直接从 main() 的参数直接传递而来,表示命令行参数个数
  • argv —  直接从 main() 的参数直接传递而来,表示命令行参数内容
  • optstring —  由选项 Option 字母组成的字符串

关于 optstring 的格式规范简单总结如下:

  1. 单个字符,表示该选项 Option 不需要参数。
  2. 单个字符后接一个冒号 ":",表示该选项 Option 需要一个选项参数 Option argument。选项参数 Option argument 可以紧跟在选项 Option 之后,或者以空格隔开。选项参数Option argument 的首地址赋给 optarg。
  3. 单个字符后接两个冒号 "::",表示该选项 Option 的选项参数 Option argument 是可选的。当提供了 Option argument 时,必须紧跟 Option 之后,不能以空格隔开,否则 getopt() 会认为该选项 Option 没有选项参数 Option argument,optarg 赋值为 NULL。相反,提供了选项参数 Option argument,则 optarg 指向 Option argument。

为了使用 getopt(),我们需要在 while 循环中不断地调用直到其返回 -1 为止。每一次调用,当 getopt() 找到一个有效的 Option 的时候就会返回这个 Option 字符,并设置几个全局变量

外部全局变量: 

extern char *optarg;
extern int optind, opterr, optopt;
  • char *optarg — 当匹配一个选项后如果该选项带选项参数,则 optarg 指向选项参数字符串;若该选项不带选项参数,则 optarg 为 NULL;若该选项的选项参数为可选时,optarg 为 NULL 表明无选项参数,optarg 不为 NULL 时则指向选项参数字符串。
  • int optind  — 下一个待处理元素在 argv 中的索引值。即下一次调用 getopt 的时候,从 optind 存储的位置处开始扫描选项。当 getopt() 返回 -1 后,optind 是 argv 中第一个 Operands 的索引值。optind 的初始值为1。
  • int opterr  — opterr 的值非 0 时,在 getopt() 遇到无法识别的选项,或者某个选项丢失选项参数的时候,getopt() 会打印错误信息到标准错误输出。opterr 值为 0 时,则不打印错误信息。
  • int optopt  — 在上述两种错误之一发生时,一般情况下 getopt() 会返回 '?',并且将 optopt 赋值为发生错误的选项。 
     

使用getopt()时,会犯的错误无外乎有两个:无法识别的选项(Invalid option) 和丢失选项参数(Missing option argument)
通常情况下,getopt() 在发现这两个错误时,会打印相应的错误信息,并且返回字符 "?" 。例如,遇见无法识别的选项时会打印 "invalid option",发现丢失参数时打印 "option requires an argument"。但是当设置 opterr 为 0 时,则不会打印这些信息,因此为了便于发现错误,默认情况下,opterr 都是非零值。

如果你想亲自处理这两种错误的话,应该怎么做呢? 首先你要知道什么时候发生的错误是无法识别的选项,什么时候发生的错误是丢失选项参数。如果像上面描述的那样,都是返回字符 "?" 的话,肯定是无法分辨出的。有什么办法吗? 有! getopt() 允许我们设置 optstring 的首字符为冒号 ":",在这种情况下,当发生无法识别的选项错误时 getopt() 返回字符 "?",当发生丢失选项参数错误时返回字符 ":"。这样我们就可以很轻松地分辨出错误类型了,不过代价是 getopt() 不会再打印错误信息了,一切事物都由我们自己来处理了。


3.2 示例 

多说无益,直接代码示例

实现一个命令,能够通过命令行参数中的选项控制打印时间的内容和格式

选项如下:

  •         -y:year
  •         -m:month
  •         -d:day
  •         -H:hour
  •         -M:minute
  •         -S:second

使用方法:

./mydate -y:打印年份

./mydate -y -m:打印年月,以空格分隔

./mydate -H -M -S:打印时分秒,以空格分隔

......

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#define TIMESTRSIZE 1024
#define FMTSTRSIZE 1024

int main(int argc, char ** argv) {

        time_t stamp;
        struct tm* tm;
        char timestr[TIMESTRSIZE];

        stamp = time(NULL);    // 获取时间戳
        tm = localtime(&stamp);    // 获取用于表示时间的结构体

        char c;
        char fmtstr[FMTSTRSIZE];
        fmtstr[0] = '\0';
        while (1) {
                c = getopt(argc, argv, "ymdHMS");    // 找到一个有效的option字符就会返回
                if (c < 0) {
                        break;
                }
                switch (c) {
                        case 'y':
                                strncat(fmtstr, "%y ", FMTSTRSIZE);
                                break;
                        case 'm':
                                strncat(fmtstr, "%m ", FMTSTRSIZE);
                                break;
                        case 'd':
                                strncat(fmtstr, "%d ", FMTSTRSIZE);
                                break;
                        case 'H':
                                strncat(fmtstr, "%H ", FMTSTRSIZE);
                                break;
                        case 'M':
                                strncat(fmtstr, "%M ", FMTSTRSIZE);
                                break;
                        case 'S':
                                strncat(fmtstr, "%S ", FMTSTRSIZE);
                                break;
                        default:
                                break;
                }
        }

        // 根据格式fmtstr格式化tm,并将结果放入容量为TIMESTRSIZE的字符数组timestr中
        strftime(timestr, TIMESTRSIZE, fmtstr,tm);
        puts(timestr);

        exit(0);

}


现在希望增加难度,当使用选项时,对特定选项需要输入选项参数

  •         -y 4:年份输出四位
  •         -y 2 :年份输出两位
  •         -H 12:十二小时制输出小时
  •         -H 24:二十四小时制输出小时
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#define TIMESTRSIZE 1024
#define FMTSTRSIZE 1024

int main(int argc, char ** argv) {

        time_t stamp;
        struct tm* tm;
        char timestr[TIMESTRSIZE];

        stamp = time(NULL);
        tm = localtime(&stamp);

        char c;
        char fmtstr[FMTSTRSIZE];
        fmtstr[0] = '\0';
        while (1) {
                c = getopt(argc, argv, "y:mdH:MS");     // 需要有argument修饰的选项后面加 ":"
                if (c < 0) {
                        break;
                }
                switch (c) {
                        case 'y':
                                if (strcmp(optarg, "2") == 0)   // 如果选项为-y,通过optarg获取选项参数
                                        strncat(fmtstr, "%y ", FMTSTRSIZE);
                                else if (strcmp(optarg, "4") == 0)
                                        strncat(fmtstr, "%Y ", FMTSTRSIZE);
                                else
                                        fprintf(stderr, "Invalid argument of -y\n");
                                break;
                        case 'm':
                                strncat(fmtstr, "%m ", FMTSTRSIZE);
                                break;
                        case 'd':
                                strncat(fmtstr, "%d ", FMTSTRSIZE);
                                break;
                        case 'H':
                                if (strcmp(optarg, "12") == 0)  // 如果选项为-H,通过optarg获取选项参数
                                        strncat(fmtstr, "%I(%P) ", FMTSTRSIZE); // 格式控制为12小时制显示
                                else if (strcmp(optarg, "24") == 0)
                                        strncat(fmtstr, "%H ", FMTSTRSIZE);
                                else
                                        fprintf(stderr, "Invalid argument of -H\n");
                                break;
                        case 'M':
                                strncat(fmtstr, "%M ", FMTSTRSIZE);
                                break;
                        case 'S':
                                strncat(fmtstr, "%S ", FMTSTRSIZE);
                                break;
                        default:
                                break;
                }
        }

        strftime(timestr, TIMESTRSIZE, fmtstr,tm);
        puts(timestr);

        exit(0);

}


现在希望继续增加难度,当指定了操作对象(非选项参数)时,则将时间输出入到指定操作对象中;否则还是输出到终端

使用方法:

./mydate -y 4:打印年份到终端

./mydate -y 4 filename:写入年份到文件 filename

需要用到如下知识点: 

If the first character of optstring is '-', then each  nonoption argv-element  is  handled  as  if it were the argument of an option with character code 1.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#define TIMESTRSIZE 1024
#define FMTSTRSIZE 1024

int main(int argc, char ** argv) {

        time_t stamp;
        struct tm* tm;
        char timestr[TIMESTRSIZE];

        stamp = time(NULL);
        tm = localtime(&stamp);

        char c;
        char fmtstr[FMTSTRSIZE];
        fmtstr[0] = '\0';

        FILE *fp = stdout;

        while (1) {
                c = getopt(argc, argv, "-y:mdH:MS");    // 需要有argument修饰的选项后面加 ":"
                if (c < 0) {
                        break;
                }

                switch (c) {
                        case 1: // 读取到非选项,非选项argv元素都会被当作字符代码为1的选项来处理,因此返回1
                                fp = fopen(argv[optind-1], "w");        // optind为下一个待读取的元素在argv的索引,因此上一个刚读取的索引应 该是optind-1
                                if (fp == NULL) {
                                        perror("fopen()");
                                        fp = stdout;    // 打开失败,说明fp还是终端
                                }
                                break;
                        case 'y':
                                if (strcmp(optarg, "2") == 0)   // 如果选项为-y,通过optarg获取选项参数
                                        strncat(fmtstr, "%y ", FMTSTRSIZE);
                                else if (strcmp(optarg, "4") == 0)
                                        strncat(fmtstr, "%Y ", FMTSTRSIZE);
                                else
                                        fprintf(stderr, "Invalid argument of -y\n");
                                break;
                        case 'm':
                                strncat(fmtstr, "%m ", FMTSTRSIZE);
                                break;
                        case 'd':
                                strncat(fmtstr, "%d ", FMTSTRSIZE);
                                break;
                        case 'H':
                                if (strcmp(optarg, "12") == 0)  // 如果选项为-H,通过optarg获取选项参数
                                        strncat(fmtstr, "%I(%P) ", FMTSTRSIZE); // 格式控制为12小时制显示
                                else if (strcmp(optarg, "24") == 0)
                                        strncat(fmtstr, "%H ", FMTSTRSIZE);
                                else
                                        fprintf(stderr, "Invalid argument of -H\n");
                                break;
                        case 'M':
                                strncat(fmtstr, "%M ", FMTSTRSIZE);
                                break;
                        case 'S':
                                strncat(fmtstr, "%S ", FMTSTRSIZE);
                                break;
                        default:
                                break;
                }
        }

        strftime(timestr, TIMESTRSIZE, fmtstr,tm);
        strncat(timestr, "\n", TIMESTRSIZE);    // fputs后面不会自动加上换行,需要手动添加
        fputs(timestr, fp);

        if (fp != stdout)    // 别忘了关闭,只有fp不是stdout的时候,才需要关闭。别忘了我们需要尽量恢复程序调用之前的状态
                fclose(fp);

        exit(0);

}


命令行参数分析部分好多

°(°ˊДˋ°) °  (இ﹏இ`。) ♡o(╥﹏╥)o ♥♡

根本记不住诶,只能多看手册 


四、环境变量

4.1 简介

环境变量的含义:程序(操作系统命令和应用程序)的执行都需要运行环境,这个环境通过多个变量来描述,这些变量就是环境变量

举例:想想生活中的“环境”是什么?

按变量的周期划为永久变量和临时性变量 2 种:

  • 永久变量:通过修改配置文件,配置之后变量永久生效
  • 临时性变量:使用命令如 export 等命令设置,设置之后马上生效。当关闭 shell 的时候失效(这种主要用于测试比较多)

按照影响范围分为用户变量和系统变量2种:

  • 用户变量(局部变量):修改的设置只对某个用户的路径或执行起作用,即仅特定用户可见
  • 系统变量(全局变量):影响范围是整个操作系统,所有用户可见

环境变量本质上是一个键值对:KEY = VALUE


4.2 查看环境变量

在 Shell 下,用 env 命令查看当前用户的用户环境变量;export 命令显示当前系统定义的系统环境变量

查看某个环境变量的

echo $KEY


4.3 environ

在 C 程序中,可以通过全局变量 char ** environ 访问环境列表

C 运行时启动代码定义了该 environ 变量并以环境列表位置为其赋值。environ 与 argv 参数类似,指向一个以 NULL 结尾的指针列表,每个指针又指向一个以空字节终止的字符串,这些字符串的内容就描述了一个又一个环境变量

可以通过下面代码查看用户环境变量 

#include <stdio.h>
#include <stdlib.h>

// 引用声明外部的变量
extern char **environ;

int main(void) {
    int i;
    for(i = 0; environ[i] != NULL; i++) {
        puts(environ[i]);
    }
    exit(0);
}


4.4 getenv

man 3 getenv

#include <stdlib.h>

char *getenv(const char *name);

功能:获取环境变量的值(VALUE)

  • name — 环境变量的 KEY
  • 返回环境变量的值 

4.5 setenv

man 3 setenv 

#include <stdlib.h>

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

int unsetenv(const char *name);    // 删除环境变量

功能:改变或添加环境变量

  • name — 指定环境变量的 KEY
  • value — 指定环境变量的VALUE
  • overwrite — 当 KEY 为 name 的环境变量已经存在时,若 overwrite 为 0,则不覆盖原环境变量的值

注意:当 KEY 为 name 的环境变量已存在且 overwrite 非 0,则说明希望对环境变量进行改变。 这里说的“改变”不是原位置改变!系统会将原环境变量储存的空间释放掉,然后新申请一片堆内存用于记录环境变量新的内容


4.6 putenv

man 3 putenv

#include <stdlib.h>

int putenv(char *string);

功能:改变或添加环境变量 

  • string — 指定环境变量。应该具有 "name=value" 这样的形式 
  • 成功返回 0;失败返回非 0 并设置 errno

和 setenv 差不多功能,不建议使用(自己想想原因)


五、C程序的存储空间布局

这部分在文件I/O那部分其实介绍过

可以结合现在介绍的,互补一下知识

可以通过命令 pmap 显示进程的所映射的地址空间,具体看手册 


六、库

动态库和静态库相关知识建议网上搜。这里主要介绍一下共享库的手动装载方式,然后分析一下 man 手册中的那个 demo 

6.1 动态库

  • 静态库的代码在编译的过程中已经载入到可执行文件中,所以最后生成的可执行文件相对较大 
  • 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库 

6.2 静态库

  • 动态库的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,所以最后生成的可执行文件相对较小 
  • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在 

6.3 共享库(手动装载库) 

6.3.1 dlopen

man 3 dlopen 

#include <dlfcn.h>

void *dlopen(const char *filename, int flags);

功能:以 flags 方式将名为 filename 的共享库装入内存,返回一个表示“已装载的库”的句柄(指针)


6.3.2 dlclose

man 3 dlclose

#include <dlfcn.h>

int dlclose(void *handle);

功能:通过表示“已装载的库”的句柄 handle,关闭该动态库 


6.3.3 dlerror

man 3 dlerror 

#include <dlfcn.h>

char *dlerror(void);

Link with -ldl.

功能:返回一个描述最后一次调用 dlopen、dlsym,或 dlclose 的错误信息的字符串 


6.3.4 dlsym

#include <dlfcn.h>

void *dlsym(void *handle, const char *symbol);
// obtain address of a symbol in a shared object or executable

功能:在已装载的库中查找特定符号 

  • handle — 句柄,用于表示某个“已装载的库”
  • symbol —  符号,通常为某个函数名
  • 如果 symbol 是个函数名,则返回指向这个函数的指针

6.3.5  man 手册中的代码示例

手动装载 math 库,获取库中 cos 函数的地址,并调用该函数

详见注释 

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <gnu/lib-names.h>  /* Defines LIBM_SO (which will be a
                                      string such as "libm.so.6") */
int
main(void)
{
    void *handle;    // 用于接收dlopen的返回值,用于表示某个“已装载的库”
    double (*cosine)(double);    // 指向函数的指针,函数指针
    char *error;

    handle = dlopen(LIBM_SO, RTLD_LAZY);    // 以执行延迟绑定的方式装载库libm.so.6
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());    // dlerror返回错误信息
        exit(EXIT_FAILURE);
    }

    dlerror();    /* Clear any existing error */

    cosine = (double (*)(double)) dlsym(handle, "cos");    // 返回cos函数的地址,并类型转换

    /* According to the ISO C standard, casting between function
       pointers and 'void *', as done above, produces undefined results.
       POSIX.1-2003 and POSIX.1-2008 accepted this state of affairs and
       proposed the following workaround:

           *(void **) (&cosine) = dlsym(handle, "cos");    // &cosine获得二级指针,二级指针转化为void **,再解引用成一级指针void *

       This (clumsy) cast conforms with the ISO C standard and will
       avoid any compiler warnings.

       The 2013 Technical Corrigendum to POSIX.1-2008 (a.k.a.
       POSIX.1-2013) improved matters by requiring that conforming
       implementations support casting 'void *' to a function pointer.
       Nevertheless, some compilers (e.g., gcc with the '-pedantic'
       option) may complain about the cast used in this program. */

    error = dlerror();
    if (error != NULL) {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }

    printf("%f\n", (*cosine)(2.0));    // 解引用函数指针,调用函数
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

七、函数跳转

先介绍一下基本函数调用和返回的过程中的函数调用栈

调用某个函数时,将该函数的调用栈帧压入函数调用栈;从该函数返回时,将该函数的调用栈帧 pop 出栈 

补充:goto 语句 

C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句

语法:

goto label;
..
.
label: statement;

在这里,label 可以是任何除 C 关键字以外的纯文本,它可以设置在 C 程序中 goto 语句的前面或者后面

但是注意:goto 只能在同一个函数内跳转,不能跨函数跳转,而有些时候跨函数跳转时很有必要的。比如在一个树中,通过递归函数遍历树并查找某个节点,如果我们在第 100W 层找到了这个节点,则从该递归函数一层一层向上返回该节点,函数调用栈不断 pop 调用栈帧,就很麻烦。如果希望能够直接将节点返回至最上层,就涉及到了跨函数跳转

下面介绍的 setjmp 和 longjmp 可以从实现一个函数到另外一个函数的跳转 


7.1 setjmp

man 3 setjmp

#include <setjmp.h>

int setjmp(jmp_buf env);

功能:设置跳转点

  • env — setjmp 函数用于将当前函数调用时候的状态记录在 jmp_buf env 中(相当于保护现场)
  • 如果直接调用该函数,则返回 0;如果是从别的地方跳回来再调用的该函数,则返回非 0 

7.2 longjmp

man 3 longjmp

#include <setjmp.h>

void longjmp(jmp_buf env, int val);

功能:跳转

  • env — 记录了一个函数调用时候的状态,即记录了一个“现场”,调用 longjmp 能够前往该“现场”
  • val — val 是调用 longjmp 时 setjmp 函数返回的值,为非零值,如果故意设置为0,也会被修改为1

感觉很抽象,没事,画图理解!

喵的还是很抽象......

那直接看代码吧


代码示例 

先看看正常的函数跳转过程

#include <stdio.h>
#include <stdlib.h>

static void d(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}

static void c(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call d().\n", __FUNCTION__);
        d();
        printf("%s():d() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}


static void b(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call c().\n", __FUNCTION__);
        c();
        printf("%s():c() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}


static void a(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call b().\n", __FUNCTION__);
        b();
        printf("%s():b() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}

int main(void) {

        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call a().\n", __FUNCTION__);
        a();
        printf("%s():a() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
        exit(0);
}

注:ANSI C 定义了许多宏。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏 

__DATE__ 当前日期,一个以 “MMM DD YYYY” 格式表示的字符串常量;
__TIME__ 当前时间,一个以 “HH:MM:SS” 格式表示的字符串常量;
__FILE__ 这会包含当前文件名,一个字符串常量;
__LINE__ 这会包含当前行号,一个十进制常量;
__FUNCTION__ 程序预编译时预编译器将用所在的函数名,返回值是字符串;

接下来我们希望:能够从函数 d 直接跳到函数 a  

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

// 用于记录跳转点的现场环境
static jmp_buf save;

static void d(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Jump now!.\n", __FUNCTION__);
        // 前往save所记录的环境(即跳转),并携带返回值为6
        longjmp(save, 6);
        printf("%s():End.\n", __FUNCTION__);
}

static void c(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call d().\n", __FUNCTION__);
        d();
        printf("%s():d() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}


static void b(void) {
        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call c().\n", __FUNCTION__);
        c();
        printf("%s():c() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
}


static void a(void) {
        // 返回值
        int ret;
        printf("%s():Begin.\n", __FUNCTION__);
        // 记录跳转点“现场”(其实就是记录一堆数据),便于从别的地方到达该“现场”
        ret = setjmp(save);

        if(ret == 0) {  // 如果直接调用的setjmp,返回0
                printf("%s():Call b().\n", __FUNCTION__);
                b();
                printf("%s():b() returned.\n", __FUNCTION__);
        } else {        // 如果是从别的地方跳回来再调用的该函数,返回非0
                printf("%s():Jumped back here with code %d.\n", __FUNCTION__, ret);
        }
        printf("%s():End.\n", __FUNCTION__);
}

int main(void) {

        printf("%s():Begin.\n", __FUNCTION__);
        printf("%s():Call a().\n", __FUNCTION__);
        a();
        printf("%s():a() returned.\n", __FUNCTION__);
        printf("%s():End.\n", __FUNCTION__);
        exit(0);
}


八、资源的获取及控制 

8.1 ulimit 命令

man 1 bash

然后进行搜索:输入/ulimit回车

可以通过 man 手册查看详细命令

先介绍一下硬性限制和软性限制

  • 软限制:软限制是内核给进程资源的限制值
  • 硬限制:硬限制是允许内核给进程资源的限制值的最大值

怎么理解这个软限制和硬限制呢?打个比方:

注意:

非授权调用的进程只能将其软限制指定为 0~硬限制范围中的某个值,同时能降低硬限制,但不能增加硬限制

授权进程(root用户)可以任意增减其软硬限制 

某个时刻对某个资源的硬限制一定大于等于软限制


几个示例

查看目前对一个进程资源的软限制

查看目前对一个进程资源的硬限制 

设置软限制

同时设置软限制与硬限制 


8.2 getrlimit

man 2 getrlimit

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);

功能:获取特定资源的限制 

  • resoure — 用于指定特定资源 
  • rlim — 指向结构体 struct rlimit 的指针,getrlimit 会将获取到的特定资源限制信息存入其指向的结构体中
  • 成功返回 0;失败返回 -1,并设置 errno

struct rlimit 结构体内容如下

struct rlimit {
    rlim_t rlim_cur;  /* Soft limit */
    rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */
};

8.3 setrlimit

man 2 setrlimit

#include <sys/time.h>
#include <sys/resource.h>

int setrlimit(int resource, const struct rlimit *rlim);

功能:设置特定资源的限制

  • resoure — 用于指定特定资源 
  • rlim — 指向结构体 struct rlimit 的指针,setrlimit 会依据其指向的结构体中的内容设置资源限制
  • 成功返回 0;失败返回 -1,并设置 errno



本章字数。。。 

就很泪目~~~,文件系统连续发了几个博文,终于讲完了,过程中感觉自己有画动漫的天赋hhh

文件系统这部分都是知识性内容,没啥难度主打一个看 man 和背

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1085389.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网收集哈希传递

1.内网收集的前提 获得一个主机权限 补丁提权 可以使用 systeminfo 然后使用python脚本找到缺少的补丁 下载下来 让后使用exp提权 收集信息 路由信息 补丁接口 dns域看一看是不是域控 扫描别的端口 看看有没有内在的web网站 哈希传递 哈希是啥 哈希…

墨者学院 WordPress 远程命令执行漏洞(CVE-2018-15877)

1. 背景介绍 近日&#xff0c;WordPress 插件Plainview Activity Monitor被曝出存在一个远程命令执行漏洞。Plainview Activity Monitor 是一款网站用户活动监控插件。 远程攻击者可以通过构造的url来诱导wordpress管理员来点击恶意链接最终导致远程命令执行 2.影响范围 Pla…

ajax同步与异步,json-serve的安装与使用,node.js的下载

20.ajax json 轻量级的数据格式做配置文件网络传输 xml 重量级的数据格式 可扩展标记语言做配置文件网络传输 现在目前主流就是大量采用json做网络传输数据格式 1.ajax的概念: 与服务器进行’通信’的一种技术,能够实现异步的刷新页面 **同步:**按照顺序一步步的执行,容易造…

Python应用-矩阵乘法-特征提取

目录 常规运算应用场景&#xff1a;特征提取特征矩阵权重矩阵举例说明代码展示 常规运算 import numpy as npmatrix1 np.array([[1, 2], [3, 4]]) matrix2 np.array([[5, 6], [7, 8]]) result np.dot(matrix1, matrix2) print(result) 输出结果&#xff1a; [[19 22][43 …

是谁还没听过杨氏矩阵~原理和实现代码都已经准备好了

有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 看到这个题目时&#xff0c;我们会马上想到暴力求解&#xff0c;即遍历这个矩阵的每…

Excel往Word复制表格时删除空格

1.背景 在编写文档&#xff0c;经常需要从Excel往Word里复制表格 但是复制过去的表格前面会出现空格&#xff08;缩进&#xff09; 再WPS中试了很多方法&#xff0c;终于摆脱了挨个删除的困扰 2. WPS排版中删除 选择表格菜单栏-选在【开始】-【排版】选择【更多段落处理】-【段…

论文阅读/写作扫盲

第一节&#xff1a;期刊科普 JCR分区和中科院分区是用于对期刊进行分类和评估的两种常见方法。它们的存在是为了帮助学术界和研究人员更好地了解期刊的学术质量、影响力和地位。 JCR分区&#xff08;Journal Citation Reports&#xff09;&#xff1a;JCR分区是由Clarivate Ana…

优雅而高效的JavaScript——箭头函数

&#x1f917;博主&#xff1a;小猫娃来啦 &#x1f917;文章核心&#xff1a;优雅而高效的JavaScript——箭头函数 文章目录 前言箭头函数的基本语法和特点箭头函数的语法箭头函数的词法绑定特性箭头函数的this值箭头函数无法使用arguments对象 箭头函数与传统函数的比较箭头函…

端口被占用?两步解决端口占用问题

第一步&#xff1a;WinR 打开命令提示符&#xff0c;输入netstat -ano|findstr 端口号&#xff0c;找到被占用端口的进程 第二步&#xff1a; 杀死使用该端口的进程&#xff0c;输入taskkill /t /f /im 进程号&#xff08; 注意是进程号&#xff0c;不是端口号&#xff09;

身份证号码,格式校验:@IdCard(自定义注解)

目标 自定义一个用于校验 身份证号码 格式的注解IdCard&#xff0c;能够和现有的 Validation 兼容&#xff0c;使用方式和其他校验注解保持一致&#xff08;使用 Valid 注解接口参数&#xff09;。 校验逻辑 有效格式 符合国家标准。 公民身份号码按照GB11643&#xff0d;…

Vue2实现图片预览功能 -- v-viewer:图片查看器

一. 先看效果图 二. 具体步骤 简介&#xff1a;一款基于 viewer.js 封装的Vue版插件&#xff0c;可用于图像查看&#xff0c;以及图片的旋转、缩放等功能预览 官网&#xff1a;v-viewer 文档说明&#xff1a;Vue图片浏览组件v-viewer&#xff0c;支持旋转、缩放、翻转等操作 - …

字符设备和杂项设备总结

字符设备是 3 大类设备&#xff08;字符设备、块设备和网络设备&#xff09;中的一类&#xff0c;其驱动程序完成的主要工作是初始化、添加和删除 cdev 结构体&#xff0c;申请和释放设备号&#xff0c;以及填充 file_operations 结构体中的操作函数&#xff0c;实现file_opera…

# Web server failed to start. Port 9793 was already in use

Web server failed to start. Port 9793 was already in use. 文章目录 Web server failed to start. Port 9793 was already in use.报错描述报错原因解决方法Spring Boot 修改默认端口号关闭占用某一端口号的进程关闭该进程 报错描述 Springboot项目启动控制台报错 Error st…

LeetCode【15】三数之和

题目&#xff1a; 解析&#xff1a; 参考&#xff1a;https://zhuanlan.zhihu.com/p/111715985 代码&#xff1a; public static List<List<Integer>> threeSum(int[] nums) {// 先排序Arrays.sort(nums);List<List<Integer>> result new ArrayLis…

房产中介租房小程序系统开发搭建

随着移动互联网的发展&#xff0c;租房小程序已经成为许多房产中介公司转型线上的重要工具。通过租房小程序&#xff0c;房产中介公司可以方便地展示房源信息、吸引租户、达成交易。那么&#xff0c;如何通过乔拓云网开发租房小程序呢&#xff1f;下面是详细的开发指南。 1.进入…

《UnityShader入门精要》学习1

读者可以在开源网站github&#xff08;https://github.com/candycat1992/Unity_Shaders_Book&#xff09;上下载本书的源代码。 第二章 渲染流水线 渲染流水线的最终目的在于生成或者说是渲染一张二维纹理&#xff0c;即我们在电脑屏幕上看到的所有效果&#xff0c;它的输入是…

微软警告国家级黑客正在利用关键的Atlassian Confluence漏洞

导语&#xff1a;近日&#xff0c;微软发布警告称&#xff0c;国家级黑客组织正在利用Atlassian Confluence的关键漏洞进行攻击。该漏洞已被微软追踪到一个名为Storm-0062&#xff08;又称DarkShadow或Oro0lxy&#xff09;的黑客组织。微软的威胁情报团队表示&#xff0c;他们自…

三次挥手和四次握手

TCP建立连接&#xff08;三次握手&#xff09; 经过DNS域名解析后&#xff0c;获取到了服务器的IP地址&#xff0c;在获取到IP地址后&#xff0c;便会开始建立一次连接&#xff0c;这是由TCP协议完成的&#xff0c;主要通过三次握手进行连接。 第一次握手&#xff1a; 建立连…

MySQL 2 环境搭建(MySQL5.7.43和8.0.34的下载;8.0.34的安装、配置教程 )

目录 MySQL的下载、8.0.34的安装及配置 1 MySQL版本介绍 2 MySQL 下载 1. 下载地址 2. 打开官网&#xff0c;点击DOWNLOADS ​编辑 3. 点击 MySQL Community Server 4. 在General Availability(GA) Releases中选择适合的版本 5.下载8.0.34和5.7.43版本 3 MySQL8.0 …

数据集笔记:分析OpenCellID 不同radio/ create_time update_time可视化

1 读取数据 &#xff08;以新加坡的cellID为例&#xff09; import geopandas as gpd import pandas as pdopencellidpd.read_csv(OpenCellID_SG.csv,headerNone,names[radio,mcc,net,area,cell,unit,lon,lat,range,samples,changeable1,created1,updated,AveSignal]) opence…