系统编程 DAY01 学习笔记

进程相关的概念

程序

是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu、内存、打开的文件、设备、锁…)

进程

是一个抽象的概念,与操作系统原理联系紧密。进程是活跃的程序,占用系统资源。在内存中执行。同一个程序也可以加载为不同的进程(彼此之间互不影响)

并发:(时钟中断 硬件手段)

单道程序设计

多道程序设计

  • 时钟终端即为多道程序设计模型的理论基础。并发时,任意进程在执行期间都不希望放弃 cpu。因此系统需要一种强制让进程让出 cpu 资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。操作系统中的中断处理函数,来负责调度程序执行。

  • 在多道程序设计模型中,进程分时复用 cpu 资源。实质上,并发是宏观并行,围观串行!

cpu/mmu:(中央处理器/内存管理单元)

  • mmu:(按照page单位分配内存)
    • 虚拟内存与物理内存的映射(pcb位于同一块物理内存里)
    • 设置修改内存访问级别

进程控制块PCB:(task_struct结构体)

  • 重点掌握如下:
    • 进程id,系统中的每个进程有唯一的id,在 C 语言中用 pid_t 类型表示,其实就是一个非负整数。
    • 进程的状态,有初始,就绪,运行,挂起,终止等状态
    • 进程切换时需要保存和恢复一些 CPU 寄存器的值
    • 描述虚拟地址空间的信息。
    • 描述控制终端的信息
    • 当前工作目录(current working directory)
    • umask掩码
    • 文件描述表,包含很多指向 file 结构体的指针
    • 和信号相关的信息
    • 用户 id 和组 id
    • 会话(Session)和进程组
    • 进程可以使用的资源上线(Resource )

环境变量

环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备一下特征。

  • 字符串(本质)
  • 有统一的格式: 名=值[:值]
  • 值用来描述进程环境信息

存储形式:与命令行参数类似。char *[]数组,数组名 environ, 内部存储字符串,NULL作为哨兵结尾。

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

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

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

练习:打印当前进程的所有环境变量。

#include <stdio.h>

extern char **environ;

int main()
{
    int i;
    for(i = 0; environ[i]; i++) {
        printf("%s\n", environ[i]);
    }
    return 0;
}

常见的环境变量

  • PATH

    • 可执行文件的搜索路径。ls 命令也是一个程序,执行它不需要提供完整的路径名 /bin/ls,然而通常我们执行当前目录下的程序 a.out 却需要提供完整的路径名 ./a.out,这是因为 PATH 环境变量的值里面包含了 ls 命令所在的目录 /bin,却不包含 a.out 所在的目录。PATH 环境变量的值可以包含多个目录,用 : 号隔开。在 Shell 中用 echo 命令可以查看这个环境变量的值:$ echo $PATH
  • SHELL

    • 当前 Shell,它的值通常是 /bin/bash。
  • TERM

    • 当前终端类型,在图形界面终端下它的值通常是 xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
  • LANG

    • 语言和 locale,决定了字符编码以及时间、货币等信息的显示格式
  • HOME

    • 当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。

getenv 函数

获取环境变量的值

  • char *getenv(const char *name); 成功: 返回环境变量的值; 失败: NULL(name 不存在)

练习:编程实现 getenv 函数

#include <stdio.h>

extern char **environ;

char *getenv(const char *name) {
    printf("%s\n", name);
    int i, j;
    for(i = 0; environ[i]; i++) {
        //printf("%s\n\n", environ[i]);
        for(j = 0; environ[i][j]; j++) {
            if(environ[i][j] != name[j]) {
                break;
            }
        }
        if(environ[i][j] == '=') {
            return environ[i];
        }
    }
    return NULL; 
}
int main()
{
    char *s = getenv("PATH");
    if(s != NULL) {
        printf("%s\n", s);
    }
    else printf("-1\n");
    return 0;
}

setenv 函数

设置环境变量的值

  • int setenv(const char *name, const char *value, int overwrite); 成功 0; 失败 -1;
  • 参数 overwrite 取值:
    • 非 0: 覆盖原环境变量
    • 0: 不覆盖。(该参数常用于设置新环境变量,如: ABC = haha-day-night)

unsetenv 函数

删除环境变量 name 的定义

  • int unsetenv(const char *name); 成功: 0; 失败: -1
  • 注意事项: name 不存在仍返回 0(成功),当 name 命名为 “ABC=” 时则会出错。

练习:熟悉上述三个函数的使用

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    char *val;
    val = getenv("ABC");
    printf("env:%s,val:%s\n", "ABC", val);
    if(setenv("ABC", "lala", 1) == -1) {
        perror("set error");
        exit(1);
    }
    val = getenv("ABC");
    printf("env:%s,val:%s\n", "ABC", val);
    if(unsetenv("ABC") == -1)
    {
        perror("unset error");
        exit(1);
    }
    val = getenv("ABC");
    printf("env:%s,val:%s\n", "ABC", val);
    
    return 0;
}

进程控制原语

fork 函数

创建一个子进程

  • pid_t fork(void);
    • 失败返回 -1;
    • 成功返回:
      • 父进程返回子进程的 ID(非负)
      • 子进程返回 0

getpid 函数

获取当前进程的 pid

  • pid_t getpid(void);

getppid 函数

获取当前进程的父进程 pid

  • pid_t getppid(void);

练习:创建 5 代进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int now;
void dfs()
{
    if(now == 5) return ;
    printf("pid==%d, ppid==%d\n", getpid(), getppid());
    pid_t pid = fork();
    if(pid == -1) {
        perror("fork error");
        exit(1);
    }
    else if(pid == 0) {
        now++;
        dfs();
    }
}
int main(void)
{
    printf("start\n");
    dfs();
    sleep(1);
    return 0;
}

getuid 函数

获取当前进程实际用户 ID

  • uid_t getuid(void);

获取当前进程有效用户 ID

  • uid_t geteuid(void);

getgid 函数

获取当前进程实际用户组 ID

  • gid_t getgid(void);

获取当前进程有效用户组 ID

  • gid_t getegid(void);

进程共享

父子进程之间在 fork 后。有哪些相同,哪些异同之处呢?

刚 fork 之后:

  • 父子相同处:全局变量、.data、.text、堆、共享库、栈、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式…

  • 父子不同处:进程ID、fork返回值、父进程ID、进程运行时间、闹钟(定时器)、未决信号集

父子进程间遵循读时共享写时复制的原则,目的节省内存开销

【重点】父子进程共享:1.文件描述符(打开文件的结构体),2.mmap建立的映射区(进程间通信详解)

特别的,fork之后父子进程谁先执行,取决于内核所使用的调度算法。

猜你喜欢

转载自blog.csdn.net/bbbbswbq/article/details/88681962