您的位置:首页 > 运维架构 > Linux

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,则进程可能会阻塞
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: