Linux操作系统-进程和信号(2)
2015-09-04 21:34
288 查看
Linux操作系统-进程和信号(2)
分类:Linux环境编程
进程的标识
有一些函数可以返回进程的标识符#include <sys/types.h> #include <unistd.h> pid_t getpid(void); //返回调用进程的进程ID pid_t getppid(void); //返回调用进程的父进程 uid_t getuid(void); //返回调用进程的实际用户ID uid_t geteuid(void); //返回调用进程的有效用户ID gid_t getgid(void); //返回调用进程的实际组ID git_t getegid(void); //返回调用进程的有效组ID
进程的状态
运行状态:指正在CPU中运行或者就绪的状态,包括:内核运行态,用户运行态,就绪态。Linux内核并不对此三种状态进行区分可中断睡眠状态:当进程处于可中断等待状态时,系统不会调度该进程执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到就绪状态
不可中断睡眠状态:不可中断,指的不是CPU不响应外部硬件中断,而是指进程不响应异步信号。处于该状态的进程不响应信号,只有使用wake_up()函数明确唤醒才能转换到就绪状态。该状态被设计用于保护内核的某些流程不被打断,或者在进行某些I/O操作时避免进程与设备交互的过程中被打断,造成设备陷入不可控状态。
暂停状态:当进程收到信号SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
僵死状态(TASK_ZOMBIE):当进程已停止运行,但其父进程还没有询问其状态时,则该进程处于僵死状态
常见的状态字符有:
STAT字符 | 说明 |
---|---|
S | 睡眠。通常是在等待某个事件的发生 |
R | 运行/可运行,即在运行队列中,处于正在运行或即将运行状态 |
D | 不可中断的睡眠(等待,不响应异步信号)。通常是在等待输入或输出完成 |
T | 停止 |
Z | 僵尸进程 |
N | 低优先级任务 |
s | 进程是回话期首进程 |
+ | 进程属于前台进程组 |
l | 进程是多线程的 |
< | 高优先级任务 |
进程的控制
system函数
在进程中执行另一个程序的一个简单方法是调用标准库函数system,原型如下:#include <stdlib.h> int system(const char *command);
解释:system函数运行command命令并等待该命令完成,
本质是(执行“/bin/sh -c command”)。system函数调用成功时返回相应命令的退出状态码,如果无法启动shell则返回127,发生其它错误时返回-1
注意:使用system函数并非启动其它进程的理想手段,因其必须启动一个shell,再使用shell执行相应的命令。下面有一个小例子:
//command.c #include <stdio.h> int main(int argc, char **argv) { printf("这条命令是由调用system函数执行的。\n"); return 0; }
将该段代码编译成command可执行文件。
//system.c #include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { printf("接下来将要调用system函数来执行一个名为command的命令。\n"); system("./command"); printf("Done.\n"); return 0; }
编译运行
biantiao@lazybone1994-ThinkPad-E430:~/Linux$ gcc -o system system.c biantiao@lazybone1994-ThinkPad-E430:~/Linux$ ./system 接下来将要调用system函数来执行一个名为command的命令。 这条命令是由调用system函数执行的。 Done. biantiao@lazybone1994-ThinkPad-E430:~/Linux$
exec函数
调用exec系列函数可以执行另外一个程序。这些函数的原型如下:#include <unistd.h> extern char **evniron; int execl(const char *path, const char *arg, ...); int execlp(cosnt char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, const char *argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[]); int execve(cosnt char *path, char *const argv[], char *const envp[]);
解释:当一个进程调用一种exec函数时,该进程将完全由新程序替换当前进程的正文,数据,堆和栈段,所以调用exec前后进程ID并未改变。
exec系列函数各参数和返回值的含义如下
- path:待运行的程序全路径名
- file:待运行的程序名,通过PATH环境变量搜索其路径
- arg:命令参数
- …:可选的一到多个命令参数,要求最后一个必须是NULL
- argv:命令参数指针数组
- envp:传递给待运行程序的环境变量指针数组
- 返回值:成功时不返回,出错时返回-1
这几个函数之间的区别
函数名包含字母p(表示path)的execlp, execvp和execvpe函数取文件名file作为第一个参数,其它函数则取路径名path作为第一个参数。当指定file作为参数时:如果file中包含/,则就将其视为路径名,否则就按PATH环境变量的设定,在相关目录中搜寻可执行文件。
函数名字中包含字母l的execl,execlp和execle要求将新程序的每个命令行参数都作为一个单独的参数,然后在最后一个参数后附加一个空指针参数结尾。
函数名中包含字母v的另外三个函数execv,execvp和execve则要求先构造一个指向各参数的指针数组,数组的最后一个元素也必须是空指针,然后以该数组地址作为这三个函数的参数。
函数以字母e结尾的三个函数execle,execvpe和execve可以传递一个指向环境字符串指针数组的指针,该指针数组也必须以空指针作为最后一个元素
fork函数
一个现存进程创建一个新进程的唯一方法是调用fork或vfork函数#include <unistd.h> pid_t fork(void);
由fork创建的新进程被称为子进程。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值是子进程的PID。
在调用fork之后,子进程和父进程继续执行fork之后的指令。
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,父进程和子进程是完全独立运行的。如果父,子进程之间相互同步,则要求采用某种形式的进程之间的通信机制。
子进程是父进程的复制品。例如:子进程获得父进程数据空间,堆和栈的复制品。注意,这是子进程所拥有的拷贝,父子进程并不共享这些储存空间。如果正文段是只读的,则父,子进程共享正文段。
Linux下的fork函数并不对父进程的数据段,堆和栈进行完全拷贝,而是使用了写时复制的技术,让父,子进程共享这些区域,而且内核将它们的存取许可权变为只读的。当有进程试图修改这些区域时,才由内核为有关部分做一个拷贝。
fork函数有以下两种用法
- 一个父进程希望复制自己,使父,子进程执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求,当请求达到时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求。
- 一个进程要执行一个不同的程序,这对shell是常见的。在这种情况下,子进程在从fork返回后立即调用exec。当然了,子进程在fork和exec之间可以更改自己的属性。
vfork函数
vfork函数与fork函数最大的一个区别是,vfork函数创建子进程后会阻塞父进程,其原型如下:#include <sys/types.h> #include <unistd.h> pid_t vfork(void);
vfork和fork一样都创建一个子进程,但是它并不会将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit,于是也就不会访问该地址空间。但在子进程调用exec或exit之前,它在父进程的空间中运行。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在子进程调用exec或exit之后父进程才可能被调度运行
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { pid_t pid; /* 用于保存PID */ char *message; /* 用于保存消息字符串 */ int n = 2; /* 技术变量 */ printf("fork program starting\n"); pid = vfork(); /* 创建子进程 */ switch (pid){ case -1: perror("fork failed.\n"); exit(1); case 0: message = "This is the child."; n = 5; break; default: message = "This is the parent."; n++; break; } for (; n > 0; n--){ puts(message); sleep(1); } exit(0); }
进程的终止状态
对于正常终止的情况,传向exit或 _exit的参数,或main函数的返回值,指明了它们的退出状态,内核以该“退出状态”作为进程的“终止状态”。在异常终止的情况下,内核(不是进程本身)会产生一个指示其异常终止原因的终止状态(termination status)。终止进程的父进程可使用wait或waitpid函数取得其终止状态。如果父进程在子进程之前终止,则它们的父进程都将改为init进程,这种处理方法保证每一个进程都有父进程。
如果子进程在父进程之前终止,那么父进程如何得到子进程的终止状态呢?答案是,当进程终止的时候,内核并未马上释放其进程控制块(PCB),而是在其中保留了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID,该进程的终止状态,以及该进程使用的CPU时间总量。
一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它扔占用的资源)的进程被称为僵尸进程(zombie)
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函数等待任一子进程的结束,waitpid函数则可等待指定的子进程的结束。
在父进程中调用wait或者waitpid可能发生如下情况:
- 阻塞(如果其所有子进程都还在运行)
- 带子进程的终止状态立即返回
- 出错立即返回(如果它没有子进程)
当一个进程终止时,内核会向其父进程发送SIGCHLD信号。父进程默认处理是忽略该信号,但也可以设置一个信号发生时即被调用的回调函数以捕获该信号。如果父进程在捕获SIGCHLD信号的回调函数中调用wait,则可期望wait会立即返回。但是在一个任意时刻调用wait,则进程可能会阻塞
相关文章推荐
- Linux(Ubuntu)下面SecureCRT 完全破解
- CentOS 5.5下安装MySQL 5.5全过程分享
- Linux压缩打包方法连载之三:bzip2, bzcat 命令
- Linux文件系统上的特殊权限(SUID、SGID、Sticky)的知识点
- linux命令,个人的日记本
- linux磁盘管理以及linux文件系统管理
- 嵌入式linux之DMA驱动
- linux 常用查看文件命令
- centos7使用无线wifi连接
- CentOS 6 系统启动流程
- Linux常见命令之关机与重启命令
- Linux系统进程管理工具
- 浅谈阅读LINUX内核源码
- linux+iptables搭建网关服务器
- Linux LVM简明教程
- Linux系统管理-(9)-yum工具
- 如何查询centos查看系统内核版本,系统版本,32位还是64位
- Linux下推荐应用程序列表【2008-07-31】
- CentOS查看CPU温度
- linux文件系统管理一创建文件