第七章:进程环境

Posted lioker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第七章:进程环境相关的知识,希望对你有一定的参考价值。

 

 

一、进程终止

进程正常终止:

1. 从main()函数返回,即retrun 0

2. 调用exit(),即在main()函数内或其它被main()函数调用的函数内调用exit()

3. 调用_exit()或_Exit(),即在main()函数内或其它会被main()调用的函数体内调用_exit()或_Exit()

4. 最后一个线程从其所在进程返回

5. 最后一个线程在其所在进程调用pthread_exit()函数

 

进程异常终止:

6.调用abort()

7.进程接收到信号

8.进程中最后一个线程最取消做出响应

 

可以使用exit()、_exit()和_Exit()函数来正常终止程序。

第一个函数和后面两个函数区别是:exit()会进行一些资源清理工作,然后返回内核,而_exit()和_Exit()则是立即返回内核。其头文件及函数原型如下:

#include <stdlib.h>

void exit(int status);
void _Exit(int status);


#include <unistd.h>

void _exit(int status);

三个函数的参数都是用于返回给操作系统,值为程序的退出状态。

 

当我们使用return n;语句或exit()函数退出程序时,可以通过ISO C标准提供的库函数来实现清理工作,该库函数会让return n;或exit()函数在程序退出时自动调用我们登记的清理函数。其函数声明如下:

#include <stdlib.h>

int atexit(void (*func) (void));

该函数负责登记程序退出时要执行的函数,若注册成功则返回0,否则返回非0。

参数是一个函数指针。atexit()函数可以登记多个清理函数,清理函数先后顺序是以栈的形式压入,即先入后出。如果函数被注册多次,那么也会被调用多次。

 

 

二、命令行参数

命令行参数是程序调用main()函数时传递的参数,如下代码可以打印输入参数:

#include <stdio.h>

/***
 * @brief  打印输入参数
 * @param  argc: 输入参数的个数
 * @param  argv: 输入参数指针数组
 * @retval 0
 **/
int main(int argc, char **argv)
{
    int i;
    for (i = 0; i < argc; ++i)
        printf("argv[%d]: %s\n", i, argv[i]);

    return 0;
}

 

在Linux命令行执行:

$ gcc test.c

$ ./a.out argv1 argv2 argv3

argv[0]: ./a.out

argv[1]: argv1

argv[2]: argv2

argv[3]: argv3

 

 

三、环境表

环境表是个指针数组,用于存储环境变量,我们可以在Linux命令行中使用命令env查看环境变量

环境表使用全局变量environ存储的,使用方式如下:

extern char **environ;

 

示例代码如下:

#include <stdio.h>

extern char **environ;

/***
 * @brief  打印环境变量
 * @param  None
 * @retval 0
 **/
int main()
{
    int i;
    for (i = 0; environ[i] != NULL; ++i)
        printf("%d: %s\n", i, environ[i]);

    return 0;
}

 

 

四、C语言的存储空间布局

1. 代码区:用于存放代码(函数)的区域,是只读区。函数指针就是指向代码区的地址

2. 全局变量区:用于存放全局变量(定义在函数外的变量)和static的局部变量

3. BSS段:用于存放未初始化的全局变量的区域BSS段在main()函数执行之前,会清0

4. 栈区(堆栈区stack):用于存放局部变量的区域,包括函数的参数和非static的局部变量

5. 堆区(heap):是程序员唯一可以控制的区域。通常内存分配、回收都是在堆区。malloc()、free()都是在堆区。堆区的内存系统完全不管,程序员全权管理堆区内存

6. 只读常量区:存放字符串字面值(""括起来的字符串)和const修饰的全局变量。这个区域也是只读区环境变量

 

需要注意的几点有:

1. const修饰的局部变量(如const int i = 1)在栈区

2. 如char a[] = "abc","abc"在只读常量区,赋值操作会把"abc"复制到栈区

3. 如char *a = "abc",char *a指向只读常量区

 

 

五、共享库

共享库,即动态链接库,在Linux系统中以.so(share object)为后缀。程序开始启动运行时,加载所需的函数。这样减少了每个可执行文件的大小。

共享库的另一个优点是可以用库函数新版本代替老版本而无需对使用该库的程序重新连接编辑。

共享库的创建命令为:

$ gcc -fpic -shared 源文件1, 源文件2, 源文件n -o lib库文件名.so(-fpic可以省略)

如:$ gcc -fpic -shared add.c -o libadd.so

$ gcc test.o -l libadd.so

 

需要注意的是,如果我们想要使用自己创建的共享库,需要需要配置环境变量LD_LIBRARY_PATH到共享库所在文件夹才能找到。否则会导致程序链接成功,运行失败。

 

 

六、存储空间分配

malloc()系列函数声明如下:

#include <stdlib.h>

void *malloc(size_t size);    /* 分配size字节的内存 */
void free(void *ptr);        /* 释放malloc()、calloc()和realloc()返回指针的内存 */
void *calloc(size_t nmemb, size_t size);    /* 分配size字节的内存并清0 */
void *realloc(void *ptr, size_t size);    /* 重新指定分配的内存大小 */

/* 例子 */
char *str = NULL;
str = (char *)calloc(10, sizeof(char));    /* 分配内存并清0 */
free(str);    /* 释放内存 */

 

malloc()系列函数需要注意的有以下两点:

1. 申请小块内存时,一次映射33个内存页,用完后继续映射。申请大块内存时(超过31个内存页)会映射比申请稍多一点的内存页;

2. 除了分配正常的内存空间,它还需要额外开辟一些空间,用于存储一些附加信息,比如分配的大小。存在前面4个字节(32位系统4个字节,64位系统8个字节)。

但是对于我们来说,正常使用即可。

 

sbrk()和brk():

#include <unistd.h>

int brk(void *addr);    /* 申请/释放内存,一般用于释放内存 */

void *sbrk(intptr_t increment);    /* 申请/释放内存,一般用于申请内存 */

/* 例子 */
char *str = NULL;
str = (char *)sbrk(4);
brk(str);

 

mmap()和munmap():

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);    /* 分配内存 */
int munmap(void *addr, size_t length);    /* 释放内存 */

/* 例子 */
char *str = NULL;
str = (char *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, NULL, NULL);
munmap(str, 4);

 

关于上述三个分配内存的函数,其实我们只需要使用malloc()系列函数即可。

malloc小于128k的内存,使用brk()分配内存,紧接着堆区;

malloc大于128k的内存,使用mmap()分配内存,在堆和栈之间找一块空闲内存分配。

 

 

七、环境变量

环境表包含环境变量名有环境变量,ISO C提供了一个库函数用于获取环境变量的值。其函数声明如下:

#include <stdlib.h>

char *getenv(const char *name);

该函数成功返回对应指针,失败返回NULL。

 

除了获取环境变量值之外,UNIX系统也提供了三个用于增加、更新和删除的库函数。 其头文件及函数原型如下:

#include <stdlib.h>

int putenv(char *string);    /* string格式为name=value,如果name已经定义,那么覆盖之前的定义 */
int setenv(const char *name, const char *value, int replace);  /* 如果replace非0,那么覆盖之前的定义;如果repalce为0,则不删除现有的定义 */
int unsetenv(const char *name);    /* 删除定义,不存在定义不算出错 */

这三个函数成功时返回0,出错返回-1。

 

每个进程都在一个环境下运行,当我们使用上面的函数更改了进程的运行环境之后,该环境会被进程的子进程继承,但不会影响到进程的父进程。

 

以上是关于第七章:进程环境的主要内容,如果未能解决你的问题,请参考以下文章

第七课 进程通信

初级学习Linux第七单元

7.7-UC-第七课:进程通信

UNIX进程环境

异常和TCP通讯

LINUX REDHAT第七单元文档