(三) Unix/Linux 进程控制

进程控制理论基础

进程特点:动态性,并发性,独立性,异步性

进程三态:就绪,执行,阻塞

进程ID:PID         父进程ID:PPID       启动进程的用户ID:UID

进程互斥是指若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他进程必须等待,知道占用该资源的进程释放资源。

临界资源——一次只允许一个进程访问的资源称为临界资源。

临界区——进程中访问临界资源的那段程序代码称为临界区。

进程同步——一组并发进程按一定的顺序执行的过程称为进程间的同步。

死锁——多个进程因竞争资源而形成的额一种僵局。

 

进程环境

每个程序都会接收到一张环境表。环境变量存在 环境表中,环境表其实就是一个字符串的数组(字符指针数组)。

extern char** environ; -> 直接拿到环境表的首地址

打印环境变量的值,可以用 echo 命令

         echo$PATH

PATH - 系统路径,系统会自动查找到PATH中的路径。因此PATH中配置的路径下的可执行文件可以省略路径直接运行。

CPATH - 头文件的系统路径。系统查找头文件时,会找它。

 

配置环境变量,在bash下:

export  环境变量名=环境变量值

注意: = 两边不要留空格

        exportPATH=.:$PATH

想永久有效,放入系统的启动文件中。系统的启动文件很多,比如:  登录目录下的.bashrc。重启机器以后或者用 source .bashrc 生效。

系统提供了关于环境变量和环境表的函数:

getenv() - 可以通过环境变量的名取得环境变量的值

putenv() setenv() -新增/修改环境变量

区别在于putenv()存在必定修改,setenv()存在可选是否修改

unsetenv() - 单删某个环境变量

clearenv() -  全部删除环境变量

 

C程序的存储空间布局:

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

2.       初始化数据段(全局区) - 用于存放全局变量(定义在函数外的变量)和static的局部变量的区域。

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

4.       栈(堆栈区stack) - 用于存放局部变量的区域,包括:函数的参数和非static的局部变量。系统自动管理栈区内存。

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

6.       只读常量区 - 存放字符串字面值(""括起来的字符串)和const修饰的全局变量。这个区域也是只读区,很多书都是把只读常量区并入代码区。

 

虚拟内存地址空间

Unix/Linux使用虚拟内存地址的方式管理内存,每个进程先天都有0到4G-1的虚拟内存地址空间,本质就是整数(编号)。虚拟内存地址本身不能存储任何的数据,必须映射到物理内存或硬盘上的文件后 才能存储数据,否则引发 段错误。

虚拟内存地址是分为用户层和内核层,用户层0-3G,3G-4G是内核层(可以设置)。用户层不能直接访问内核层,通过系统函数可以。内存的基本单位是字节,内存地址是逐字节的,但内存映射的基本单位不是一个字节,是 4096个字节(4k),叫一个内存页。函数getpagesize()可以查看内存页的大小。

 

进程的内存空间排列次序:(地址从小到大)

代码区、只读常量区、全局区、BSS段、堆、 ....... 栈

   

内存中的每个进程,都在 /proc 目录下建立一个目录,目录名就是进程的ID(PID),进程结束 目录就消失。这个目录就是进程的文件方式。cat 命令 查看 /proc/进程ID/maps就可以看到进程的内存分配情况。函数 getpid()可以获取当前进程的ID

 

段错误的引发原因:

1.       使用没有映射的虚拟内存地址  存储/获取数据。

2.       对内存区域进行没有权限的操作。比如:修改只读区。

 

 C语言的字符串有三种形式:

 字面值("abc")、字符指针(char*)、字符数组(char a[])

 字符串必须有一个结尾,以'\0'结尾。关于字符串的基本操作写在string.c中。

 

存储器分配

内存管理的相关函数

      STL  ->  全自动管理 (C++后期)

        |

     C++ ->  new分配,delete回收内存

        |

     C语言-> malloc()分配,free()回收内存

        |

  Unix系统函数 -> sbrk() / brk() 分配和回收内存

        |

  Unix系统函数 -> mmap()映射物理内存/硬盘文件,munmap()解除映射

                                      (用户层函数到此为止)

 ------------------------------------------------------------------------------------------------------

Unix内核层函数  ->kmalloc()/vmalloc()  (内核层,嵌入式)

 

malloc() -> 传入分配内存的大小(字节),返回分配内存的首地址。

内存分配的函数干两件事情:

1.       分配虚拟内存地址  -  所有情况下

2.       映射物理内存/硬盘文件 - 第一次映射,后面用完了再映射

malloc()申请小块内存时 一次映射33个内存页(4k*33),用完后继续映射。申请大块内存时(超过31个内存页) 会映射 比申请稍多一点的内存页。malloc()函数除了分配正常的内存空间,还需要额外开辟一些空间,用于存储一些附加信息,比如分配的大小。存在前面4个字节。

free()函数 - 释放内存

free() 一定会释放虚拟内存地址,以便虚拟内存地址可以循环利用,不一定解除 映射。超过33个内存页的部分会释放,最后33个内存页不释放,直到进程结束时释放。

 

sbrk() 和 brk() - Unix的系统函数

sbrk()和brk() 系统的底层会维护一个位置,通过位置的移动完成内存的分配和回收。映射内存时 以一个内存页作为基本单位。

void* sbrk(intincrement)

参数是增量

增量为正数时,分配内存

增量为负数时,回收内存

增量为0时,取当前的位置

返回 移动之前的位置(可用内存的首地址),这个返回值对于增量为负数的情况没有意义。

sbrk() 在分配内存时很方便,但在回收内存时比较麻烦;

brk()则相反-> 开发中,一般用sbrk()分配内存,用brk()回收内存。

brk()的使用方式就是直接传递一个地址过来,做新的位置。

brk()必须和sbrk()结合使用,获得第一个位置。

mmap() 和 munmap() - Unix的系统函数,更贴近底层

  

void* mmap(void*addr,size_t size,int prot,int flags,int fd, off_t offset)

addr:可以指定映射的首地址,一般为0 交给内核指定。

size :就是分配内存的大小,映射时以 页 为单位。

prot:是分配内存的权限,一般用PROT_READ|PROT_WRITE  

flags:是标识,通常包括以下三个:

MAP_SHARED/MAP_PRIVATE :二选一,指明映射的内存是否共享,MAP_SHARED只对映射文件有效;MAP_ANONYMOUS: 映射物理内存,默认映射文件。

fd:是文件描述符,在映射文件时有用。

offset:是文件的偏移量,指定映射文件时从哪里开始。

映射物理内存时,fd和offset给 0 即可。

成功返回首地址,失败返回 MAP_FAILED==(void*)-1

如果有多个权限、选项的拼接,一般设计的方式就是:

每种权限/选项 用一个二进制表示(一位是1,其他全0),用位或运算连起来。

 

 

进程终止

正常退出:

1.       在main()中执行return 语句

2.       执行exit()函数

3.       执行_Exit()或_exit()函数

4.       最后一个线程从其启动例程返回

5.       最后一个线程调用pthread_exit() 退出

非正常退出

1.       调用abort()

2.       接受一个信号并终止

3.       最后一个线程对取消请求做出响应,即被取消。

_exit()和_Exit()是一样的,前者由POSIX说明,后者是标C。

_Exit()是立即退出,不做清理处理;

exit()则先执行一些清理处理(调用各终止处理程序,关闭标准I/O流等)

int  atexit( void (*func) (void)

可以使用atexit()函数注册一些终止处理函数,这个函数在exit()之前会被自动调用,return也会调用。

 

进程控制编程

Linux/Unix用 ps 命令查看进程。

ps 只能显示当前终端启动的进程。

ps -aux :Linux专用选项,Unix不直接支持 (/usr/ucb/ps可以用)

ps -ef  : Unix/Linux通用的选项

进程比较多 可以用 管道实现分页,命令:

 ps -aux | more

 whereis 可以查看 命令在哪个目录下 

其中进程的状态主要包括:

S - 休眠状态,进程大多数处于休眠状态

          s- 说明该进程有子进程(父进程)

          R- 正在运行的进程

          Z- 僵尸进程 (已经结束但资源没有回收的进程)

 

关于父进程和子进程

操作系统中的多进程是有启动的次序的,Unix/Linux系统先启动0进程,0进程再启动进程1和进程2(有些系统只启动进程1),然后0进程就休眠。进程1和进程2 启动其他的进程,其他进程再启动其他的进程,直到所有进程都启动为止。

父子进程的关系:

 fork()创建子进程后,父子进程同时运行,如果子进程先结束,子进程会给父进程发信号,父进程回收子进程的资源。

 fork()创建子进程后,父子进程同时运行,如果父进程先结束,子进程变成孤儿进程,认进程1(init进程)做新的父进程。

 fork()创建子进程后,父子进程同时运行,如果子进程结束时父进程没有收到信号或没及时处理,子进程将变成僵尸进程。

 

获取进程ID

pid_t getpid(void)获取本进程ID

pid_t getppid(void) 获取父进程ID

 

 

创建进程

pid_t fork (void)

fork被调用一次却返回两次,返回子进程的PID或者0,失败返回 -1 。

fork()创建子进程时,会复制除了代码区之外的所有区域,包括缓冲区。

fork()创建子进程时,如果父进程有文件描述符,子进程会复制文件描述符,不复制文件表(父子进程共用一个文件表)。

pid_t vfork(void)

与fork区别:

fork 子进程拷贝父进程的数据段,vfork子进程与父进程共享数据段,在调用exit()或exex()函数之前,在父进程空间中运行

fork父子进程执行顺序不确定,vfork子进程先运行,父进程后运行

注: vfork()如果占用的是父进程的资源,必须用exit()显式退出。

 

**调用fork两次以避免僵死进程。如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,那就调用fork两次。

 

创建子进程的方法:

1.       fork()  创建子进程,通过复制父进程创建子进程。因此父子进程对应相同的代码区。

2.       vfork()+ execl() 创建启动全新的子进程,父进程和子进程的代码区完全不同,父子进程执行的是完全不同的代码。(比采用写时复制的fork 更快)

 

fork()创建的子进程会复制父进程的虚拟内存地址,但映射到不同的物理内存上,同时把原来的值拷贝过来(即虚拟内存地址是相同的但实际上在不同的物理内存上)复制完成以后父子进程的内存就独立了。

 

wait()和waitpid()

wait()和waitpid()可以让父进程等待子进程的结束,并取得子进程的退出状态和退出码(return后面的值或exit()中的值)。

wait()和waitpid()的区别在于wait()很固定,而waitpid()更灵活。wait()是等待任意一个子进程的结束后返回,包括僵尸子进程。而waitpid()可以选择等待的子进程,也可以不等待。

pid_t wait(int* status)

参数是一个传出参数,用来取得结束子进程的退出码和退出状态;

返回值,有结束子进程就返回它的PID,没有结束就等待,父进程自己阻塞,如果出了错 返回-1.

宏函数WIFEXITED(status)可以判断是否正常退出,

              WEXITSTATUS(status) 可以取到退出码。

waitpid() 有更多的选择

pid_t waitpid(pid_t pid,int* status,int option)

参数 status 和wait()一样,pid可以设定等待哪个哪些子进程,option可以设定是否等待。

pid的值可能是:

   ==-1  等待任意子进程,与wait()等效

   >0      等待指定子进程(指定pid)

   ==0   等待本进程组的任一 子进程

   <-1    等待进程组ID等于pid绝对值的任一子进程

后两种情况了解即可,比较少使用。

option的值:

 0                         阻塞,父进程等待

WNOHANG      不阻塞,直接返回0

返回值: 有子进程结束时返回 子进程的pid;出错返回 -1.

如果阻塞方式,没有子进程结束继续等待;

如果是 WNOHANG,没有子进程 返回0.

 

exec函数族

vfork() 和 execl()的合作方式:

vfork()可以创建新的进程,但没有代码和数据,execl()创建不了新进程,但可以为进程提供代码和数据 。

execl()是exec系统函数中的第一个,功能是启动一个全新的进程,替换当前的进程。新的进程会全面覆盖旧进程,但不会新建进程。(会替换各种内存区域,但进程ID不变)

execl("程序的路径","执行命令","选项","参数",NULL);

只有第一个参数是必须正确的,第二个参数必须存在但可以不正确,第三个和第四个参数可以没有,NULL代表参数结束了。

比如运行我们的程序:

execl("./b.out","b.out",NULL)

 

进程时间

任一进程都可以调用times函数获得它自己及已终止子进程的时间(墙上时钟时间、用户CPU时间和系统CPU时间)

       clock_t  times(struct  tms  *buf)              times函数返回墙上时钟时间。

tms结构如下:

struct  tms  {

       clock_t tms_utime;        //用户CPU时间

       clock_t tms _stime;        //系统CPU时间

       clock_t tms_cutime;             //子进程终止的用户时间

       clock_t tms_sctime;

}

clock_t的值都用_SC_CLK_TCK变换成秒数,即

clktck =sysconf(_SC_CLK_TCK)  获得每秒时钟滴答数

times(..)/clktck 即秒数


1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、 1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READmE.文件(md如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值