linux 进程

来源:互联网 发布:mac搭建apache服务器 编辑:程序博客网 时间:2024/05/22 13:29

GNU/Linux 进程模型

 

GNU/Linux进程有两种基本类型,内核线程和用户进程。内核线程是在内核中由kernel_thread()函数创建,用户进程由fork()和 clone()创建。(讨论用户进程)

         创建一个子进程(由fork创建),就创建了一个新的子任务,并为它复制了父任务使用的内存。两个进程使用的内存是相互独立的在调用fork的时候,父进程当时的所有变量对子进程都是可见的;但在fork执行完成之后,父进程的变量的任何变动对子进程都是不可见的。

         在创建一个新任务的时候,父进程使用的内存并不是真正的复制给子任务,他们都指向同一处内存空间,但把内存页面标记为copy-on-write。当任何一个进程试图向这些内存中写入内容时,就会产生一组新的内存页面由这个进程私有。默认情况下,子进程继承文件描述符、内容映像以及CPU状态。

        

GNU/Linux中每一个进程都有一个唯一的描述符,称为进程ID(或进程号id),每一个进程都有一个父进程(init进程除外)。

函数getpid()获得当前的进程号;函数getppid()获得当前进程的父进程的进程号,函数getuid()获得用户id,函数getgid()获得组id

 

用fork创建一个子进程

 

API函数fork返回时,已经分裂出了新的进程,但fork的返回值指明的是进程运行的上下文环境。

Pid_t  pid;

….

Pid = fork();

If (pid > 0)

{

         //父进程的上下文,子进程号为pid

}

Else if (pid == 0)

{

         //子进程上下文

}

Else

{

         //父进程上下文,但fork调用出错,没有创建子进程

}

 

对于父子进程中共有的变量,如果对该变量发生写操作,则会将内存划分开,每个进程拥有各自的内存,这些内存相互独立。即每个进程都为自己复制一份独有的变量集。

 

创建者进程同步

         在父进程上下文环境中调用了wait函数,函数wait把父进程挂起,直到子进程退出。如果父进程没有调用wait函数等待子进程退出,子进程就会成为“僵尸进程”(即既不是活的,也不是死的)。允许僵尸进程存在会导致问题,因为他们浪费了资源。因此需要正确处理子进程的退出操作,如果父进程先于子进程退出了,已经运行的子进程会视为继承自init进程。

【另一种避免僵尸进程的方法是告诉父进程,在子进程产生退出信号时忽略他们,这可以使用信号API函数完成。】

 

函数wait会把调用者挂起(这里即为父进程),等待子进程退出。在子进程退出之后,表示其特定退出状态的整数型值会传递给函数wait。

int status;

pid_t  pid;

…..

Pid = wait(&status);

If (WIFEXITED(status))

{

         Printf(“Process%d exited  normally\n”, pid);

}

 

捕获信号

 

信号,即GNU/Linux中进程的回调符号。可以为某个进程注册为在某事件发生时接收信号,或是在某个默认操作退出时忽略信号。

         为了捕获信号,要为进程注册一个信号句柄(一种回调函数)以及感兴趣的具体信号。

示例:为捕获信号注册句柄

#include<stdio.h>

#include<sys/types.h>

#include<signal.h>

#include<unistd.h>

Void         catch_ctlc(int   sig_num)

{

         printf(“CaughtControl – C\n”);

         fflush(stdout);

}

Int main()

{

         signal (SIGINT,  catch_ctlc);

         printf(“Goahead, make my  day.\n”);

         pause();

         return  0;

}

程序中注册了信号SIGINT,该信号表示接受到了Ctrl + C

使用API signal函数注册了句柄 signal(SIGINT,  catch_ctlc);

首先指定感兴趣的信号,然后句柄函数就可以对这个信号有反应;

再使用pause,将进程挂起直到它接收到了一个信号。

接受到信号进入句柄处理函数,可以把一个消息发送给stdout,然后清空其缓冲区以确保内容显示出来。从信号句柄返回后,main函数可以从pause语句处继续运行并正常退出。

 

发出信号

可以在一个进程中使用API函数kill向另一个进程发出信号。API函数kill需要给出一个进程ID和要发送的信号。

: kill( pid_t  pid,  SIGNAL sig);

如果想要向自己发送一个信号(同一个进程),可以使用API函数raise这个函数可以发出信号而无需指定进程ID参数(该参数由getpid()函数自动获取)。

 

 

传统的进程API

API函数

用途

Fork

创建一个新的子进程

Wait

将进程挂起直到子进程退出

Waitpid

将进程挂起直到指定的子进程退出

Signal

注册一个新的信号句柄

Pause

将进程挂起直到捕获到信号

Kill

向某个指定的进程发出信号

Raise

向当前进程发出信号

Exec

将当期进程映像用一个新的进程映像替换

Exit

正常终止当前进程(退出)

 

Fork函数:

Fork()调用复制了父进程,然后返回对某个特定进程的控制(父进程或子进程)。如果fork的返回值小于零,说明发生了错误。Errno的值可能是EAGAIN 或 ENOMEM,均是由于可用内存不足造成的。

         API函数fork在GNU/Linux中效率很高。在调用fork的时候并不立即复制内存页表,父进程和子进程当时共享相同的页表,只是不允许对这些页表进行写操作。但出现对共享页表的写操作时,会为进行操作的进程复制一份页表以供其私有。“当写入时复制”(copy – on-write),允许fork函数很快的完成运行。只有在共享数据内存出现写操作时,内存才会发生页表的分页。

 

Wait函数

API函数wait用于将调用进程挂起,直到子进程(由调用进程创建)退出,或直到某个信号发出。如果父进程没有在等待子进程退出,而子进程又退出了,这个子进程就会成为僵尸进程。

         Wait函数提供了一种同步机制,如果子进程在父进程调用wait函数之前退出了,这个子进程会成为僵尸进程。如果现在再调用wait还是可以释放资源,这种情况下,wait直接返回。

Pid_t wait(int *status);

函数wait返回推出的子进程的ID 值,如果发生错误则返回-1,参数status中包含有关子进程退出的状态信息。

 

评估wait函数所用的宏函数

说明

WIFEXITED

如果子进程正常退出,则不为0

WEXITSTATUS

返回子进程的exit状态

WIFSIGNALED

如果子进程因为信号二结束,则此宏值为true

WTERMSIG

返回引起子进程退出的信号(尽在WIFSGNALED为true时有意义

 

Waitpid函数

API函数waitpid是挂起父进程直到某个指定的子进程退出。

Pid_t waitpid(pid_t  pid,  int * status, int options);

Waitpid的返回值是退出的子进程的进程描述符,如果options参数设定为WNOHANG,则返回值为零,且没有子进程退出(waitpid会立即返回)。

         Waitpid需要的参数由pid值,一个status参数(用来保存返回值)以及options参数。参数pid的值可以是子进程的ID,也可以是表示其他行为的值。

Waitpid的pid参数值

说明

>0

挂起直到由pid指定的子进程退出

0

挂起直到任何一个与调用进程的组ID相同的子进程退出

-1

挂起直到任何子进程退出(与wait功能相同)

< -1

挂起直到任何一个其组ID与pid参数的绝对值相同的子进程退出

 

Pause函数

函数pause将进程挂起,直到接收到信号。在信号接收到以后,调用进程从pause中返回,继续运行。API函数pause的原型如下:

Int  pause(void );

如果进程为捕获信号已经注册了信号句柄,那么pause函数会在信号句柄被调用并返回之后返回。

 

Kill函数

API函数kill向一个进程或一系列进程发送信号,如果信号成功发送了返回0,否则返回 -1.函数kill的原型:

Int  kill(pid_t   pid,  int sig_num);

参数sig_num表示要发送的信号,参数pid可以是各种不同的值

Pid

说明

>0

信号发送到由pid指定的进程

0

信号发送到与调用进程同组的所有进程

-1

信号发送到所有进程(init进程除外)

<0

信号发送到由pid的绝对值指定的进程组中所有进程

 

Exec变体

API函数fork提供把应用程序分裂为独立的父进程和子进程的机制,两个进程分享共同的代码却可以扮演不同的角色。Exec系列函数则用于完全替换当前进程映像。

//虽然exec函数会返回当前pid,它实际上是开始了一个新程序,用它来替换了当前进程。

 

Exec变体的原型:

Int execl(const char* path, const char*arg, …)

Int execlp(const char* path,  const char *arg, ….);

Int execle(const char* path,   const char* arg, …. Char* const envp[]);

Int execv(const char* path,  char* const argv[]);

Int execvp(const  char* file, char* const argv[]);

…….

Exec命令允许把当前进程上下文替换为第一个参数指明的程序或命令:

execl(“/bin/ls”, “ls”,  “-la”, NULL);

这个命令用 ls映像(列出目录)替换了当前进程。第一个参数指定了将要执行的命令(包含路径),第二个参数是这个命令的名字;第三个参数是传给ls 的选项,最后用NULL指明参数列表结束。在应用程序中执行这句代码的结果就是执行了命令 ls –la

 

Alarm函数

函数alarm在其他函数超时的情况下非常有用。函数alarm在预先设定的事件长度达到时会发出一个SIGALRM信号。

unsigned int  alarm(unsigned  int secs); //在secs秒之后发出SIGALRM信号。

用户要传入一个秒钟数,即在发送SIGALRM信号前等待的秒数。如果前面没有出现警告情况,alarm函数会返回零,否则它返回前面的警告所等待的秒数。

 

 

Exit函数

API函数exit终止调用进程。传入exit的参数会返回给父进程,为wait或waitpid调用提供所需要的状态信息。

Void exit (int  status);

进程调用exit时还会向父进程发出SIGCHLD信号,释放当前进程占用的资源。如果进程注册了atexit 或on_exit函数,这些函数会在退出时调用(调用顺序与他们的注册顺序相反)。

原创粉丝点击