进程相关的概念
程序
是指编译好的二进制文件,在磁盘上,不占用系统资源(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之后父子进程谁先执行,取决于内核所使用的调度算法。