Linux环境编程之进程
来源:互联网 发布:制导算法 编辑:程序博客网 时间:2024/05/21 04:22
一.概念
什么是进程,什么是程序。这个在操作系统的书籍上有很多种阐述。程序是一个包含可执行代码的文件,它放在磁盘等介质上。当程序被操作系统装载到内存并分配给它一定资源后,此时可称为进程。为方便操作系统管理,每个进程都会有一个唯一的非负整数编号。程序是一个静态概念,进程是一个动态概念。
理解进程之前先了解一下什么是用户空间和内核空间,我们知道32位系统最大的寻址空间是4G,在Linux系统中,0~3G为用户空间,3~4G为内核空间,当进程陷入内核时,我们可以认为内核代表进程的运行。
在Linux中有很多方式在表示一个进程在运行中,进程描述符:当进程产生时有Linux操作系统分配。内存:用来存放进程要执行的代码和使用的数据。文件描述符:进程运行时打开的文件。认证信息:用户和组ID。进程执行环境:各种环境变量。资源安排:CPU时间
Linux的每一个进程都存在一个状态,只要该进程与运行中,就肯定存在一种状态。运行状态:当进程正在被运行或者已经处于可调度状态。
可中断状态:进程正在等待一个信号或者资源。不可中断状态:不可被信号唤醒,一般用于硬件初始化时。暂停状态:当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU信号后就会进入TASK_STOPPED状态,可向其发送SIGCONT信号让进程转换到可运行状态 。僵尸状态:当进程已经运行结束,但其父进程还未查询其状态。见下图所示。
进程的布局:所有的C语言代码实现基本都是按照这样的存放方式放置在内存中。栈用来存放局部变量和函数的返回地址。地址从高到低生长。堆是一块连续的内存,有低地址向高地址生长。需要程序在运行时动态申请和释放。数据段存放了程序运行时的各种数据。代码段存放了可执行指令,一般为只读。如下图所示.
二.有关基础函数
以上所述都是操作系统的基本概念,我想对于学过计算机操作系统的童鞋们一定功底比我扎实,毕竟俺不是计算机科班出身,半路出家的我也只好把这些基本概念理解一下下。下面会多用代码说话,少用文字。以下所有代码都可以编译通过,可以作为学习linux编程的一种范例,仅供参考。
1.进程环境变量:
环境变量和命令行参数都放在进程的高地址。环境变量可用 environ来引用。以name=string的形式存放。
2.程序的启动和终止
一个进程的开始是以程序的启动为开始,程序的结束为终止。下图可以详细说明一个程序的生命周期。一个程序是其实就是内核和应用层之间的交互,这里他们的接触方式就是系统调用。程序是从main函数开始,到exit函数截止,最后内核会回收进程的所有资源。
3.进程的终止
Linux进程终止函数 exit(status) _exit(status)
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
参数status为进程返回状态,可在shell用$?来获取。_exit 为系统调用,此函数直接进入内核终止进程。exit为glibc库函数,它会先运行注册函数,也有可能会进行文件流的关闭操作,之后再调用_exit系统调用。
4.终止处理程序atexit函数
atexit注册一个进程正常终止时要调用的函数,一个进程最多可注册32个函数。
#include <stdlib.h>
int atexit(void (*function)(void));
成功返回0,失败返回非0。
参数为函数指针,此函数会在进程调用exit时调用。在main函数返回出,分别用return exit(0) _exit(0),看其中的区别。_exit函数是不会调用注册函数的。
5.关于环境变量
getenv用来获取指定环境变量。
#include <stdlib.h>
char *getenv(const char *name);
参数name为环境变量的名称,如 PATH,SHELL 等。如果环境变量存在那么返回该环境变量的值,否则为NULL
setenv用来设置环境变量,会把value拷贝到环境变量所在的内存区域。
#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
参数name为环境变量的名称,value为该环境变量的值。如果环境变量已经存在,overwrite非0时,改变该环境变量的值。overwrite为0时,什么都不做直接返回。成功返回0,失败返回-1(一般为放置环境变量的内存空间不够)。环境变量都是以 name=string的形式存放。
putenv 无论环境变量是否存在,都会使设置值生效。
#include <stdlib.h>
int putenv(char *string);
参数string的格式为 name=string。成功返回0,失败返回非0
putenv() 函数并不拷贝环境变量字符串到进程环境表,只是存放环境变量数值的指针,而setenv()函数则完全拷贝环境变量字符串到进程环境表。分别用 gcc putenv.c 和gcc putenv.c –DLOCAL –g编译,产看运行结果。把putenv.c中的putenv换成setenv看结果。
清除环境变量
clearenv清除所有的环境变量, 并且把environ设置为NULL。unsetenv清除名字为name的环境变量。
#include <stdlib.h>
int clearenv(void);
int unsetenv(const char *name);
6.跨函数跳转
goto只能在函数中跳转,如果要求从一个函数跳到另外一个函数?用setjmp和longjmp。跨函数的跳转主要用在调用函数层次较深时,为了节约函数的返回时间。
setjmp函数
setjmp用来设置返回点,保存当前的寄存器值。
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
参数jmp_buf env用来保存当前寄存器值。longjmp会根据env跳转到setjmp处。一个setjmp可以对应n个longjmp,可以用setjmp的返回值来区分。如果成功setjmp返回0,如果是从longjmp返回的,那么返回值有longjmp的第二个参数决定。所以longjmp的第二个参数不可以为0,否则无法判断setjmp是如何返回的。
longjmp函数
longjmp的第一个参数是setjmp返回的,第二个参数是给setjmp的返回值。
当longjmp返回后,setjmp所在函数中的自动变量恢复到调用setjmp时的值。如果变量是保存在内存中的,那么它的值仍然是调用longjmp的时候的值。当你希望变量值在setjmp和longjmp时仍然保持其值,但必须用volatile说明该变量,并且需打开-O优化选项。gcc setjmp.c 和 gcc setjmp.c –O观察运行结果。
三.多进程编程
Linux操作系统会为每个进程维护一个进程控制块(俗称PCB,不是印制电路板,而是process control block)。进程控制块保存了进程执行过程中的某个瞬间。操作系统因此可以做进程的切换。见下图。
1.进程ID
getpid得到自己的进程ID,getppid得到自己父进程ID
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
返回值为进程ID
用户和组ID
getuid返回实际用户ID, geteuid返回有效用户ID。getgid返回实际组ID,getegid返回有效组ID。
#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
2.进程的创建
你可以在shell里敲入命令的方式来创建进程,也可以在在程序中通过调用fork系统调用来生成新的进程 。新产生的进程我们称他为子进程。init进程的进程ID为1,是一个特殊的用户进程。它会收集孤儿进程(父进程已退出的子进程),并结束它们。
fork函数(十分著名的函数)
当你的程序执行到fork语句时:操作系统会复制一个与父进程完全相同的子进程。 新进程和原有进程共享代码空间, 可执行程序是同一个程序。Linux操作系统会为新进程产生一个ID和进程控制块。当子进程或者父进程不进行写操作时,父子进程共享一分数据,当有些操作时,数据会分离(称为写时复制Copy-On-Write),互不干涉。子进程/父进程对数据所做的任何修改,都不会影响另一方。
fork会产生一个新的进程。
#include <unistd.h>
pid_t fork(void);
返回值为-1时,创建子进程失败。返回0时,子进程开始执行。返回值 >0时,父进程开始执行。父子进程都是从fork之后开始执行。到底是父进程还是子进程先开始执行,要看操作系统的调度算法。
父进程或者子进程有些操作时,子进程/或父进程将新产生一份进程的数据拷贝,然后再修改。见下图。
子进程的继承:文件描述符,实际用户/组ID,有效用户/组ID,附加组ID,控制终端,根目录,信号屏蔽和安排,存储映射,资源限制。
vfork函数
vfork和fork一样都是创建一个子进程,但子进程不会从父进程复制任何东西。子进程完全和父进程共享数据和堆栈,子进程对数据的修改不会触发写时复制,也就是说子进程所作的修改会出现在父进程中。
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
返回值为-1时,创建子进程失败。返回0时,子进程开始执行。返回 >0时,父进程开始执行。
vfork和fork的区别
vfork产生的子进程必须以exit或者exec返回,否则会出现未定义错误。vfork一定保证子进程先运行,在子进程调用exit或者exec之前父进程是不可能运行的。vfork的使用场合是子进程不会用到父进程任何资源的情况下。vfork只产生一个进程控制块,然后再通过exec产生子进程所需要的资源和代码。当vfork的子进程用return返回时,看有什么结果?
3.父子进程的终止
父子进程谁先终止时未定义的,如果父进程先于子进程终止。那么子进程会被init进程“领养”,子进程的父进程id变为0。如果父进程不去查询子进程的状态,那么子进程一直会处于“僵尸”状态。这也是init进程存在的原因之一。父进程可以通过wait和waitpid函数来显示的查询子进程。
wait和waitpid可查询子进程的结束状态,如果子进程还处于运行状态,那么父进程则会阻塞。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait函数只要任意一个子进程结束,就会返回该子进程ID,如果出错则返回-1。status为子进程返回的状态,需要通过宏WIFEXITED和WEXITSTATUS最终获得返回状态。
waitpid可以指定某个进程id或者进程组id
参数解析
pid==-1 等待任意一个进程结束,此时与wait等效。
pid>0 等待与pid相符的子进程结束。
pid==0 等待组ID等于调用者进程组ID的任意进程结束。
pid<-1 等待组ID等于|pid|的任意进程结束。
4.exec函数
fork进程后,往往通过exec函数执行另一个程序。此时该子进程完全被替换为新程序。exec用一个全新的程序替换当前进程的正文,数据,堆和栈。exec有6中变体可以使用,俗称为exec函数。
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execve(const char *path, char *const argv[], char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
下图说明exec函数关系
execl和execv,这两个函数的区别在于程序的命令行参数如何传递。l代表list,意味着execl的每个命令行参数都是单独传入。v代表vector,所有命令行参数打包成 char *argvp[]的方式传给execv。其实execl所做的是把单独的命令行参数打包后传给execv。execlp和execvp,p代表path,也就是说你只要在第一个参数中指明可执行文件的名字,系统便会从PATH指定的路径中寻找那个可执行文件并执行。不设置environ,观察程序运行区别execle和execve,函数的最后一个参数为环境变量。
5.system函数
system执行外部一个命令或者程序,相当于fork, exec,waitpid。
#include <stdlib.h>
int system(const char *command);
返回-1表示失败,成功返回命令的返回状态。如果command中没有路径符/,则从PATH环境变量指定的目录中寻找该命令。
最后是一个思考题,就是下面代码中一共创建了多少个进程
练习题:使用fork,exec,waitpid函数实现一个简单的system函数。
编写一段程序,创建一个“僵尸”进程,然后调用system执行ps命令验证改进程是“僵尸”进程。验证完后父进程用wait或者waitpid对 该“僵尸”进程“收尸”。
参考文献:unix环境高级编程
- Linux环境编程之进程
- Linux环境编程之进程间通信
- Linux系统编程学习之《进程环境》
- Linux环境编程--进程
- Linux环境编程之进程(六):进程组
- Linux环境编程之进程(七):守护进程
- Linux之进程环境
- Linux环境编程之进程(二):程序的存储空间布局
- Linux环境编程之进程(三):函数间跳转
- Linux环境编程之IPC进程间通信(二):管道
- Linux环境编程之IPC进程间通信(三):FIFO
- LINUX环境编程之进程控制(上)
- Linux环境编程--进程通信
- Linux环境编程之进程(四):创建新进程、执行程序和进程终止
- Unix环境编程之进程环境
- UNIX环境高级编程之进程环境
- Unix环境编程之 进程环境
- Linux/UNIX之进程环境
- nutch windows win7 配置 错误 java.lang.NoClassDefFoundError
- 七个受用一生的心理寓言
- 网络驱动模型
- 编译qvfb出现cannot find -lXtst解决
- C_EXTERN
- Linux环境编程之进程
- 用Word 2003给文档添加文字和图片水印
- [工会网站建设方案-日志]第一阶段 策划:收集、整理、分析
- java 线程Thread.Sleep详解(转载)
- 各种电子图书馆文章转为Word文档的方法
- 请问大家在开发的时候一般多点的复选框都怎么写?
- 安装ubuntu 10.04
- ubuntu下虚拟xp,以及解决ubuntu硬盘温度过高问题
- .NET开发者常会忽略的几个错误