一.进程概述
1.概念:进程是操作系统资源分配的基本单位,是一个动态的实体,是程序的一次执行过程.
父进程:就是当前运行的程序;
子进程:当前运行的程序中创建的新进程
2.进程标识
用户ID:
实际用户ID(uid):运行该程序的用户的ID;
有效用户ID(euid):标示程序以什么身份(普通用户,root用户等等)来运行,该身份的ID.
组ID:
实际组(gid):实际用户的所属组的组ID;
有效组ID(egid):有效用户所属组的组ID.
3.Linux进程的结构
组成:
代码段:存放程序的可执行代码;
数据段:存放全局变量,常量,静态变量;
堆栈段:堆存放动态分配的内存变量,栈存放函数参数及函数内部定义的局部变量,用于函数调用.
4.Linux进程状态
R:运行状态 S:可中断等待状态 D:不可中断等待状态 Z:僵死状态 T:停止状态
状态字符:
<:高优先级进程 N:低优先级进程 L:内存锁页(页不可被换出内存) s:会话首进程 l:多线程进程 +:进程位于前台进程组
5.Linux下程序转化为进程的过程
内核将程序读入内存-->内核为该进程分配进程标识符(PID)和其他所需资源-->内核为该进程保存PID及相应的状态信息并把进程放到队列中等待执行.
6.进程的内存映像(低地址到高地址)
代码段(二进制机器代码)-->数据段(已被初始化的变量,包括全局变量和静态变量)-->未初始化数据段(未被初始化的静态变量,也被成为bss段)-->堆(动态分配的变量)-->栈(保存函数的返回地址,参数内部定义的局部变量)-->高地址(命令行参数和环境变量)
二.进程操作
1.创建进程
#include
#include
pid_t fork(void);
pid_t vfork(void);
执行成功后返回 父进程调用fork()函数的返回值(子进程的ID) 和 子进程中fork()函数的返回值(0),创建失败返回-1.
vfork()实际上也是调用了fork().
二者区别:
fork创建进程时,子进程继承了父进程的全局变量和局部变量,子进程有自己独立的地址空间,不管是全局变量还是局部变量,子进程与父进程对他们的修改互不影响;
vfork创建进程时,子进程共享父进程的地址空间,子进程修改变量对父进程是可见的(可以理解为变量是子父进程共有的),并且保证子进程先运行,调用exec或者exit之后,父进程才可能被调度运行.
注意:fork()之后先执行父进程还是子进程取决于内核的调度算法.
--守护进程后面博客单独总结
2.进程退出
正常退出:在main函数中执行return,调用exit或者_exit函数;
异常退出:调用about函数,进程收到使该进程终止的信号.
各种退出方式的比较:
exit是一个函数,执行后把控制权交给系统有参数,return是执行完函数后的返回,把控制权交给调用函数;
exit是正常终止,about是异常终止,exit在头文件stdlib.h中声明,执行时先进行一些清除操作再将控制权交给内核,_exit在头文件unistd.h中,执行时会立即将控制权返回给内核.
3.执行新程序
exec调用:系统调用exec函数简单来讲就是用原来的进程运行新的程序.
"原来"表达的是进程还是那个进程,没有创建新进程,所以进程ID还是原来那个;
"新的程序"指的是系统将进程中原来的代码段换位新的代码段,废除原来的数据段和堆栈段,为新的程序分配堆栈段和数据段,原有子进程的内存映像被新程序替代.
---环境变量
对于环境变量的概念,我觉得书上的叙述对于我这种新手来说有点不容易理解,所以我还是拿自己的话说一下,不过这些是我自己的理解,如有错误或者不当之处还麻烦指出!
所谓"环境"变量,顾名思义,环境就是大家都能享受,使用的东西,自然界的环境是指你生存空间的一种状态;同理,操作系统中的环境指的就是同一台计算机上各个用户都能享受,使用的东西,就是你在计算机上工作的空间状态.自然界中的环境可以被改变,比如增加植树,树就是自然界的环境变量;同理,操作系统中也有类似"树"的东西,系统中的这些可以被用户自行设置的"树"就是环境变量.
在这个地方为什么要讲环境变量呢?
因为在进程中执行新程序的时候,新程序的运行环境和原进程的运行环境是一样的,即环境变量未改变,结合上面exec调用来讲,就是:调用exec函数执行新程序时,新程序的运行环境,进程都同原来程序的一样,改变的只有进程的结构部分!
对exec函数族的理解:
其实我个人认为,各个函数族都是有一个基本函数,然后其他的函数都是从基本函数上改变 某些参数类型 和 参数传递方式 来实现原有函数的功能的.
exec也不例外,execve就是基本函数,它的功能就是当原程序运行到execve函数这里的时候,给原程序一个新的入口(也可以理解为一条路),然后程序又开始从这个入口处运行,原程序的execve后面的语句就不再执行.当然,给了一个新的入口,肯定要保证"环境和主人公"不变,所以execve函数将原来程序的 命令行参数 和 环境变量 通过实参传给新程序.当然,给了一条新路当然要告诉那条路怎么走,也就是新的可执行程序的完整路径,所以参数中也包含新可执行程序的路径.这样,execve的参数传递的总共就是 环境变量 命令行参数 新程序的路径 三个数据,其他函数原理都是这样,只不过实现方式不同.比如,execve把命令行参数用数组一次传递,execl是将命令行参数一个一个分开传递.
4.等待进程结束
#include
#include
pid_t wait(int *statloc); (wait用来使父进程暂停执行,等待直到它的第一个子进程结束为止,返回的是终止运行的子进程的PID)
pid_t waitpid(pid_t pid, int *statloc, int options);(waitpid用来等待特定的子进程结束,特定的子进程就是参数pid指定的进程,statloc指向的变量存放所等待的子进程的退出码)
三.进程的其他操作
1.获得进程ID
#icnlude
#include
pid_t getpid(void);
系统调用getpid用来获取当前进程的ID.
2.设置进程ID
#include
#include
int setuid(uid_t uid);(设置实际用户ID和有效用户ID)
int setgid(gid_t gid);(设置实际组ID和有效组ID)
调用规则:
若进程具有root权限,则函数将实际用户ID和有效用户ID都设置为uid;
若进程不具有root权限,但uid等于实际用户ID,则函数只将有效用户ID设置为uid,不改变实际用户ID;
若不满足上述条件之一,则调用失败,返回-1.
注意:
1)内核通过进程的有效用户ID来检查进程是否有权访问某文件;
2)对于调用了setuid函数的程序,当进程的有效用户ID是euid即root用户时,如果使用setuid函数使euid为其他非root用户,则该进程从此就不具有root权限了.
3.改变进程的优先级
#include
int nice(int increment); (nice用于将进程原来的优先级变成 原来的优先级+increment的值)
#include
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
getpriority用于获得并返回一组进程的优先级,which和who的取值及意义如下:
PRIO_PROCESS:一个特定的进程,who的取值为进程ID;
PRIO_PGRP:一个进程组的所有进程,who的取值为进程组ID;
PRIO_USER:一个用户拥有的所有进程,who取值为实际用户的ID.
调用成功返回指定进程的优先级,出错返回-1.
setpriority用于设置指定进程的优先级,which和who的取值及意义同上,参数prio为要 设定的优先级的值.
nice的系统调用等价于:
int nice(int increment)
{
int oldprio = getpriority(PRIO_PROCESS, getpid());
return setpriority(PRIO_PROCESS, getpid(), oldprio + increment);
}
阅读(1456) | 评论(0) | 转发(0) |