linux程序设计-Process

要求掌握:

熟悉进程结构,类型,及调度

通过哪些不同的方法可以开始启动新的进程

了解什么是父进程,子进程,及僵尸进程

什么是信号量,及怎么去使用它们。

一、       进程结构类型及调度

(一)  进程

l  概念:每一个正在运行中的程序的实体组成了一个进程

进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制。

l  结构:

1.      每一个进程有一个独一无二的代号(PID):当进程开始时,linux系统从2到32768按顺序寻找并分配下一个空闲着的PID,到达最后允许的代号时绕回2重复寻找。PID为1的进程是init进程,init进程是用于管理其他进程的,在系统启动后自动运行。

2.      代码是被作为只读形式加载到内存

3.      系统库是可以被分享的。因此在内存中,即使很多个进程调用里面同一个命令,在内存中只需要一份命令副本就行了。这使得一个程序可以变得更小,因为它不需要包含分享库代码。

4.      进程拥有属于自己的栈空间和环境空间:栈空间用于函数中的局部变量和控制函数的调用和返回。

5.      一个进程可以拥有一个以上的线程的执行

6.      进程被管理来适应物理内存:因为linux有一个虚拟内存系统,把一些代码及数据按页分配在硬盘里,所以需要一个管理机制来使进程适应这样的存储方式。

(二)  进程表

u 在linux内核里面有一张像数据结构来描述所有进程的当前情况,包括了PID,状态等等,系统通过使用PID作为索引在进程表寻找相应的进程项。进程表是有限制大小的,所以系统所支持的最大进程数就受到限制了。

(三)  进程类型

PS:      进程查看命令。

     By default, the ps programshows only processes that maintain a connection with a terminal, a console,aserial line, or a pseudo terminal. Other processes run without needing tocommunicate with a user on a terminal. These are typically system processesthat Linux uses to manage shared resources. You can use ps to seeall such processes using the -e optionand to get “full” information with -f.

         $ ps ax 简洁显示进程的状态信息


交互进程:由一个Shell启动的进程,交互进程既可以在前台运行,也可以在后台运行。

批处理进程:这种进程和终端没有联系,是一个进程序列。

监控进程:也称守护进linux或者unix操作系统中在系统的引导的时候会开启很多服务,并在后台运行,这些服务就叫做守护进程

(一)    进程调度(ProcessScheduling

linux中,进程的运行时间不可能超过分配给他们的时间片,他们采用的是抢占式多任务处理,所以进程之间的挂起和继续运行无需彼此之间的协作。

在一个如linux这样的多任务系统中,多个程序可能会竞争使用同一个资源,在这种情况下,我们认为,执行短期的突发性工作并暂停运行以等待输入的程序,要比持续占用处理器以进行计算或不断轮询系统以查看是否有输入到达的程序要更好。我们称表现好的程序为nice程序,而且在某种意义上,这个nice 是可以被计算出来的。操作系统根据进程的nice值来决定它的优先级,一个进程的nice值默认为0并将根据这个程序的表现不断变化。长期不间断运行的程序的优先级一般会比较低

you can see that the oclock programis running (as process 1362) with a default nice value. If it had been startedwith the command

$ niceoclock &

$ renice number PID

其中,参数numbernice命令的number意义相同。

注:

1用户只能对自己所有的进程使用renice命令。

2 root用户可以在任何进程上使用renice命令。

(3)    只有root用户才能提高进程的优先权。

二、启动新的进程

u 通过system系统调用来创建一个新的进程。

#include <stdlib.h>

int system (const char *string);

这个系统调用通过执行以字符串形式传入的命令来创建新的进程,自身进程一直等待直到新进程执行完毕system返回后才继续向下执行。

Ø For example

         #include<stdlib.h>

#include <stdio.h>

int main()

{

printf(“Running ps with system\n”);

system(“ps ax”);

printf(“Done.\n”);

exit(0);

}

   这段代码先打印出Running…后,调用system通过shell来启动命令创建新进程,然后旧进程一直等待新进程执行完毕system返回后才开始往下执行自己的代码答应出Done后退出。

如果希望在创建新进程后旧进程能够无需等待新进程执行完毕就能继续运行,可以把通过shell把新进程放在后台运行,如system(“ps ax &”);这样旧进程就能在几乎新进程开始启动的时候继续执行下去,这时的新旧进程基本是不相干的了。

通过调用exec家族函数来创建新进程代替当前进程,当新进程开始执行后,旧进程不会在被执行。(其中新进程沿用旧进程PID及nice value,The new process started by exec inheritsmany features from the original. In particular, open file descriptors remainopen in the new process unless their “close on exec flag” has been set (referto the fcntl system callin Chapter 3 for more details). Any open directory streams in the originalprocess are closed.)

   #include<unistd.h>

char**environ;

intexecl(const char *path, const char *arg0, ..., (char *)0);

intexeclp(const char *file, const char *arg0, ..., (char *)0);

intexecle(const char *path, const char *arg0, ..., (char *)0, char *const

envp[]);

intexecv(const char *path, char *const argv[]);

intexecvp(const char *file, char *const argv[]);

intexecve(const char *path, char *const argv[], char *const envp[]);

这些函数集属于两个类型,execl,execlp和execle他们的变量形参数是以NULL指针最为结束的。execv,execvp则是通过使用第二个(元素为字符串的数组)形参来传入数据的。

以p为后缀的使用环境变量PATH作为搜索可执行文件的路径,如果可执行文件没有在PATH路径下的,file就必须包含整个绝对路径(其中注意路径下需要把可执行文件包含进去)。以e为后缀的函数,最后一个参数可以作为新进程的环境变量设置(其中都要记得以0作为结束指针),通过传入字符串数组来实现。

Forexample

#include <unistd.h>

/* Example of an argumentlist */

/* Note that we need aprogram name for argv[0] */

char *const ps_argv[] =

{“ps”, “ax”, 0};/*Example environment, not terribly useful */

char *const ps_envp[] =

{“PATH=/bin:/usr/bin”, “TERM=console”, 0};/*Possible calls to exec functions */

execl(“/bin/ps”, “ps”, “ax”, 0); /* assumes ps is in /bin */

execlp(“ps”, “ps”, “ax”, 0); /* assumes /bin is in PATH */

execle(“/bin/ps”, “ps”, “ax”, 0, ps_envp); /* passes own environment*/

execv(“/bin/ps”, ps_argv);

execvp(“ps”, ps_argv);

                                     execve(“/bin/ps”, ps_argv, ps_envp);

使用exec家族函数的参数总大小是受到限制的,在linux的环境变量ARG_MAX给出,exec家族通常没有返回除非出现了error,具体出现了哪种error在error变量errno会被相应设置上,exec家族return-1。

三、复制一个进程映像

u使用fork系统调用来创建一个新的进程,这个系统调用复制当前进程,并且在进程表里面创建一个新的表项,这一项中有很多特性是跟当前进程一样的,新的进程执行同样的代码,不过新进程拥有属于自己的数据空间,环境和文件描述符。

#include <sys/types.h>

#include <unistd.h>

pid_tfork(void);


当前进程调用fork后,在父进程可见的返回是一个新的PID(子进程PID),在子进程可见的返回是0。通过判断fork的返回值可以区分出子进程与父进程。(如果fork失败则返回-1)

Ø 下面是一个典型的fork代码组织结构:

pid_t new_pid;

new_pid = fork();

switch(new_pid) {

case -1 : /* Error */

break;

case 0 : /* We are child */

break;

default : /* We are parent */

break;

u 等待当前进程下任一个子进程的完成

#include<sys/types.h>

#include <sys/wait.h>

pid_twait(int *stat_loc);

当父进程调用wait系统调用后,父进程暂停运行,一直等待直到其中一个子进程的完成,这时wait返回此进程的PID,并且子进程的退出状态信息会被保存在stat_loc指针里,父进程可以通过调用以下的宏定义来判断子进程的完成状态。


以下是一段例子代码:

#include<sys/types.h>

#include<sys/wait.h>

#include<unistd.h>

#include <stdio.h>

#include<stdlib.h>

intmain()

{

pid_tpid;

char*message;

intn;

intexit_code;

printf(“fork program starting\n”);

pid= fork();

switch(pid)

{

case-1:

perror(“fork failed”);

exit(1);

case0:

message= “This isthe child”;

n =5;

exit_code= 37;

break;

default:

message= “This isthe parent”;

n =3;

exit_code= 0;

break;

}

for(;n > 0; n--) {

puts(message);

sleep(1);

}

This section of the program waits for thechild process to finish.

if(pid != 0) {

intstat_val;

pid_tchild_pid;

child_pid= wait(&stat_val);

printf(“Child has finished: PID =%d\n”, child_pid);

if(WIFEXITED(stat_val))

printf(“Child exited with code%d\n”, WEXITSTATUS(stat_val));

else

printf(“Child terminatedabnormally\n”);

}

exit(exit_code);

}

$ ./wait

forkprogram starting

Thisis the child

Thisis the parent

Thisis the parent

Thisis the child

Thisis the parent

Thisis the child

Thisis the child

Thisis the child

Childhas finished: PID = 1582

Childexited with code 37

$

 

u 等待特定的子进程结束

#include<sys/types.h>

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *stat_loc,int options);

pid:为要等待结束的子进程PID,为-1时,相当wait;

stat_loc:与wait一样;

options:提供了一些额外的选项来控制waitpid

目前在Linux中只支持WNOHANG和WUNTRACED两个选项,

这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

  ret=waitpid(-1,NULL,WNOHANG |WUNTRACED);   

如果我们不想使用它们,也可以把options设为0,如:   

ret=waitpid(-1,NULL,0); 如果使用了WNOHANG参数,调用waitpid,即使没有子进程退出,它也会立即返回(此时如果等待的子进程没有退出则返回0,否则返回PID),不会像wait那样永远等下去。而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD 

 

三、     僵尸进程(Zombie processes)

u 当一个子进程在父进程正常结束之前就已经完成了,那么这个子进程会成为僵尸进程,虽然子进程不在执行了,但是在进程表里还是保留了它的表项,子进程仍然存在系统里面是因为它的退出码还需要留着被使用倘若父进程在后来调用了wait。只有直到他的父进程正常结束了或者调用了wait,或者waitpid,或者显示忽略该信号才能清除僵尸进程。

u 当父进程被异常终止时,旗下的所有子进程都会重新设置父进程PID为1.也就是说子进程都被init进程领养了,但仍然是僵尸进程,不过在init的这些僵尸进程是可以在被init收集时清除的。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值