4.1 前言

本章讨论进程概念、资源、属性。

4.2 内核和进程的关系

当系统启动时,内核代码被加载到内存,初始化之后,启动第一个用户进程,然后内核的代码就等着用户进程来调度了。

4.3 进程是程序的实例

当程序员编写好一个程序,编译之后会生成这个可执行程序,这个程序可以被运行。

运行程序其实是用户进程(Shell进程)指示内核要启动另一个用户进程,内核便为这个新的进程分配资源,并加载该进程的代码和数据。

一个程序可以被运行多次。

4.3 进程资源

4.3.1 PCB

进程运行时,内核为进程每个进程分配一个PCB(进程控制块),描述进程的信息。

PCB在内核中对应的结构体是task_struct

4.3.2 虚拟地址空间

每个进程都会分配虚拟地址空间,在32位机器上,该地址空间为4G。

更细节的图例

在进程里平时所说的指针变量,保存的就是虚拟地址。当应用程序使用虚拟地址访问内存时,处理器(CPU)会将其转化成物理地址。

int* p = malloc(100);
*p = 100;
访问内存时,系统会做地址转换。

这样做的好处在于:

  • 进程隔离,更好的保护系统安全运行

  • 屏蔽物理差异带来的麻烦,方便操作系统和编译器安排进程地址

思考:如果实现一个智能的myfree函数,该函数会自动判断指针是否在堆上还是在栈上,还是在全局变量中。

4.3.3 CPU

CPU的分配是动态的,不是进程一加载就直接分配的,一般来说每个系统都会有许多进程同时在运行,而CPU只有一个(多核CPU可以认为是多个,但是数量远少于进程数量)。那么,进程就需要排队等待,就好像有100个人,在4个卖饭的窗口买饭一样。

内核将进程PCB放入一个队列,总是让CPU服务队列中的第一个进程,服务时间可以是10毫秒,可以是25毫秒,具体多长时间跟具体系统有关系,这个时间有个名字叫做时间片。一旦这个进程服务时间到,这个进程会被丢到队列尾部,进行排队。进程调度。

内核中有一个常量HZ,一般是100,250, 1000

4.4 进程属性和状态

进程有许多的属性和状态,具体可以看task_struct,这里挑一些常见的进行讲解。

4.4.1 PID

进程编号,内核为每个进程分配一个进程编号,这个是进程的身份证,系统保证了不会重复分配。
通过函数getpid或者命令ps可以查看进程的PID。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    pid_t pid = getpid();
    pid_t ppid = getppid();
    printf("%d\n", (int)pid);
    printf("%d, %d\n", (int)pid, (int)ppid);
}

4.4.2 PPID

PPID就是父进程ID,在Linux系统中,除了内核启动的第一个进程,其它进程都有父进程。
通过函数getppid或者命令ps可以查看进程的PPID。

4.4.3 账户ID/组ID

账户分实际账户和有效账户两种,如果你使用test账户登陆系统,但是使用sudo运行程序时,实际账户时test,有效账户时root。

通过函数getuidgeteuid获取真实账户id和有效账户id
通过函数getgidgetegid获得真实账户id和有效账户id
通过setuidsetgidseteuidsetegidsetreuidsetregid等设置进程的有效和真实账户id。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> int main()
{
uid_t uid = getuid();
uid_t euid = geteuid();
printf("uid =%d, euid=%d\n", (int)uid, (int)euid);
}

4.4.4 进程组ID/会话组ID/控制终端

进程组:getpgrpsetpgid
会话组:getsidsetsid
控制终端:

4.4.5 环境变量

保存该进程运行的环境信息。
进程的环境变量保存在全局变量environ中,
也可以通过setenvgetenvunsetenv进行设置和获取。

4.4.6 进程状态

#define TASK_RUNNING            0 可运行状态,相当于进程三种状态的执行和就绪状态
#define TASK_INTERRUPTIBLE      1 中断等待状态。处于这种状态唤醒的原因可能是信号或定时中断,或者I\O就绪
#define TASK_UNINTERRUPTIBLE    2 不可中断等待状态,主要是等待I\O
#define TASK_ZOMBIE             3 僵死状态,进程已经结束已经释放除了PCB以外的部分系统资源,等待父进程wait()读取结束状态
#define TASK_STOPPED            4 进程已经停止
注意:这是0.11的内核,在1.0的内核以上就多了一种状态,在1.0内核的sched.h中有定义
#define TASK_SWAPPING           5 交换状态,进程的页面也可以从内存转换到外存,以空出内存空间

启动进程时,该进程在RUNNING状态,RUNNING状态的进程有可能时正在被执行,或者在队列中排队。但是如果进程调用阻塞函数,而运行条件不满足时,该进程会进入挂起状态。挂起状态的进程不再分配CPU,除非等到运行条件满足时。会阻塞进程运行的函数有许多,比如getchar是典型的阻塞调用。

阻塞函数列表可以在man 7 signal中,找到关于阻塞函数的列表。

4.4.7 文件描述符

在进程控制块中,有一个数组保存着打开的文件描述符信息。

4.4.8 进程时间

进程有一些字段,用来记录进程的运行时间。
通过times可以获取进程从运行开始时到执行times函数时,所花费的时间。这个在系统性能优化时特别重要。
简单的程序可以从time命令获取进程的运行时间。
Linux时间相关函数可以从man 7 time获取。

4.4.10 当前工作目录和根目录

当前工作目录是相对地址的相对目录,通过getcwd函数可以获取当前目录,也可以通过pwd或者echo $PWD获取。也可以通过chdir来修改当前工作目录。
根目录是绝对地址的相对目录,可以通过chroot来修改根目录。调用chroot需要root权限。

目录相关资料在man 7 path_resolution

4.5 动态库和静态库

当使用动态库时,系统会检查该动态库是否已经加载,如果已经加载,则直接映射即可,如果没有加载,那么会加载之后再映射。

如果动态库中有全局变量,那么该全局变量对于不同的进程来说,是相互独立和隔离的。

链接静态库时,静态库被一起编译进可执行程序,运行时不再依赖静态库。

动态库编译:
gcc -fpic -shared a.c b.c -o libtest.so
链接动态库
gcc main.c -ltest -L. -o mybin
运行程序时

export LD_LIBRARY_PATH=.
或者将动态库拷贝到/usr/lib
./mybin

静态库打包:
ar rcs libtest.a a.o b.o

链接库时,如果有同名的动态库和静态库,默认优先动态库,如果要链接静态库,那么使用-static,比如

gcc a.c -lmylib -static

通过以下方式可以指定某些库使用静态链接,而某些库使用动态链接

-Wl,-Bstatic -ltest -Wl,-Bdynamic -ltest2

4.6 内存管理

进程运行时,总是占用内存,无论是加载代码,还是在函数中定义局部变量,还是调用malloc申请内存。

无论是那种原因,进程需要使用内存时,它将向系统申请,并获得相对应的虚拟地址,而进程只能访问虚拟地址,真实的内存地址,进程无法访问。当进程访问虚拟地址时,系统会负责进行虚拟地址到物理地址的转换,系统发现进程尝试访问非法地址,那么进程将得到惩罚(段错误)。

这样做保护了系统的稳定性,不会因为个别新手程序员导致整个系统的崩溃。

另外还有一个好处是,使用虚拟内存之后,每个进程的导致空间是一致的,简化了进程的设计。

相关函数:mallocbrkmmapalloca

4.7 进程总结

从用户的角度看,一个程序跑起来就是进程。而从操作系统的角度看,进程是一个控制块+代码+数据的组合。

4.8 函数和命令

4.8.1 函数

getpid:获取进程ID
getppid:获取父进程ID

getuid:获取实际用户ID
getgid:获取实际组ID
geteuid:获取有效账户ID
getegid:获取有效组ID

进程组描述了一项任务
getpgrp:获取进程组号
setpgid:设置进程组号

setsid:设置Session号
getsid:获得Session号

getcwd:获取当前工作目录
chdir:设置当前工作目录
chroot:修改当前根目录

getenv:环境中取字符串,获取环境变量的值
setenv:改变或增加环境变量
unsetenv
extern char** environ(全局变量)

malloc/free:堆区申请内存
mmap/munmap:在映射区申请内存
brk:全局区申请内存
alloca:在栈上申请内存

int foo(int len)
{
//  char buf[len];
    char* buf = alloca(len);
}

4.8.2 命令

ps axu:现行终端机下的所有程序,以用户为主的格式来显示程序状况,显示所有程序,不以终端机来区分
ps ajx
grep:搜索
kill:杀死进程(给进程发送信号)

最新文章

  1. MVC4与JSON交互的知识总结
  2. devstack查看服务日志
  3. traits的使用
  4. 求任意长度数组的最大值(整数类型)。利用params参数实现任意长度的改变。
  5. rotate array 旋转数组
  6. Android IOS WebRTC 音视频开发总结(二三)-- hurtc使用说明
  7. 【BZOJ】【1055】【HAOI2008】玩具取名
  8. Python在centos下的安装
  9. Oracle用户的单张表的读写权限控制
  10. C/C++ 不带参数的回调函数 与 带参数的回调函数 函数指针数组 例子
  11. [转]带花树,Edmonds&#39;s matching algorithm,一般图最大匹配
  12. Base62编码与62进制
  13. tomcat install on Linux
  14. Elasticsearch .Net Client NEST 索引DataSet数据
  15. eclipse 启动tomcat后 页面无法访问tomcat首页
  16. Java 反射 Method的invoke回调调用任意方法
  17. 利用Flume采集IIS日志到HDFS
  18. 如何利用docker快速构建MySQL主从复制环境
  19. Java并发编程笔记——技术点汇总
  20. JAVA课程设计--------五子棋

热门文章

  1. css过渡样式
  2. BGCN Rec:模型结构概述
  3. 【python】Ubuntu中多条命令的运行
  4. 使用python获取window注册表值的方法
  5. 基于BP神经网络的手MNIST写数字识别
  6. Xpath 高级用法
  7. 接入监控视频,为啥还需要对接厂商的SDK呢,不是有onvif这样的标准协议吗?
  8. 前端面试HTML和CSS总结,这一篇就够了!
  9. mindxdl--common--log_record.go
  10. 2018 Web开发人员学习路线图