`
iwindyforest
  • 浏览: 235177 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

进程控制入门

 
阅读更多

进程控制入门

进程系统

Linux是个多任务多用户的操作系统,系统直接管理的每个任务的最小单位,就是进程(process)。每个进程都有一个惟一的标识符pid,不同的进程pid不相同,在Shell下输入ps -A,可以显示当前的所有进程。一个进程不代表一个应用程序(application),因为一个应用程序可能对应多个进程,也不代表一个可执行文件(executable file),因为一些可执行文件可以被同时运行多个,它们是互不相干的。

在Linux中,进程不是相互独立的,每个进程(除了init进程)都有一个父进程(parent process),同时每个进程可以有0个1个或多个子进程(child process)。换句话说,Linux的进程是一个树形结构,在Shell下输入pstree可以查看这个树的形状。下图为pstree返回结果的一部分。

    init-┬-NetworkManager-┬-dhclient
    │                └-{NetworkManager}
    ├-SystemToolsBack
    ├-avahi-daemon---avahi-daemon
    ├-bonobo-activati---{bonobo-activati}
    ├-console-kit-dae---63*[{console-kit-dae}]
    ├-hald---hald-runner-┬-hald-addon-acpi
    │                    ├-hald-addon-cpuf
    ├-pulseaudio-┬-gconf-helper
    │            └-2*[{pulseaudio}]
    ├-rsyslogd---2*[{rsyslogd}]
    ├-seahorse-daemon
    ├-telepathy-gabbl
    ├-telepathy-haze-┬-telepathy-haze
    │                └-{telepathy-haze}
    ├-trashapplet
    └-wpa_supplicant

在C语言中,获得当前进程的pid的函数是pid_t getpid(void);,获得当前进程的父进程的pid的函数是pid_t getppid(void);,两者都在unistd.h中声明。
用户和权限

因为Linux是多用户的系统,所以内核中有着强大的用户控制,因此每个进程还有一个所有者,即实际用户ID(uid)。系统uid是一个整数,不同于用户名。默认情况下进程的uid继承于父进程。例如我用所有者为byvoid(uid为1000)的bash终端启动了一个进程,那么这个进程的uid也是1000。用户uid可以通过uid_t getuid(void);函数获得。如果权限满足,程序在运行时可以修改uid,C语言函数为int setuid(uid_t uid);,如果成功执行返回0,否则返回-1。只有具有root用户权限的进程可以设置uid。

除此以外,进程还有一个有效用户ID(euid)。euid是决定进程文件系统权限的身份,一般情况下进程euid和uid是相同的。在C语言中可以通过uid_t geteuid(void);函数获得进程euid。同样euid也可以修改,函数为int seteuid(uid_t uid);仅当当前uid和euid中至少有一个为0(root)时,才可以设置euid。有一种特殊情况,就是一个二进制可执行文件所有者为root,并且被chmod +s后,在一般用户身份下执行,这时产生的进程uid为一般用户,而euid为0(root),这种情况下该进程具有和root一样高的权限。
进程生成
fork函数

Linux允许用户创建用户进程的子进程,在C语言中通过pid_t fork(void);函数实现。fork函数的基本功能是生成一个子进程,并复制当前进程的数据段和堆栈段,子进程和父进程共用代码段。因为复制了堆栈段,所以父进程和子进程都停留在fork函数的栈帧中,fork函数要返回两次,一次在父进程中返回,一次在子进程中返回,但是两次的返回值是不一样的。在父进程中,fork函数返回值为子进程的pid(如果成功调用的话),在子进程中,fork函数的返回值为0。因此可以根据返回值的不同确定程序的运行流程。父进程和子进程默认情况下是同步执行的,由系统内核调度,哪个先执行是未知的。因为父子进程的数据段和堆栈段都是独立的,所以两者互不干涉,各行其是,内存不能直接共享。
执行程序

Linux中要执行一个外部程序,必须生成一个子进程,因为内核执行程序的命令exec会替换掉当前进程的地址空间的所有内容并继续执行,执行另一个程序意味着当前程序不再执行。在C语言中,并没有exec这样的一个函数,而是有下列一组函数。

    int execl (const char * file,const char * arg,…);
    int execlp(const char * file,const char * arg,…);
    int execle(const char * file,const char * arg,…,NULL,char * const envp[ ]);
    int execv (const char * file, char * const argv[ ]);
    int execvp(const char *file ,char * const argv []);
    int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

其中以execl开头的函数,第一个参数file为可执行文件名,接下来有若干个参数,分别为传入的argv[0],argv[1],argv[2],…,最有以NULL结束。如果file参数为路径名(其中包含’/'),execl函数会直接定位到文件并执行,否则仅在当前目录下寻找文件,而execlp函数遇到文件名则会按照环境变量PATH的顺序寻找。execle最后一个参数为二维字符数组,表示传递给程序新的环境变量列表。execv,execvp,execve和前三者用法相似,只不过不以可变参数列表的方式传递参数,换以二维字符数组。上述函数执行失败后会返回-1,如果执行成功的话将会不返回,因为代码段已经被新的可执行程序替换。
进程阻塞
wait函数

在实际的应用中,有时候需要让父进程停下来等待子进程的执行完毕,这时候就需要进行进程阻塞(process blocking)。C语言中使用pid_t wait(int *statloc)函数可以得到子进程的结束信息。调用wait函数的进程会阻塞,直到该进程的任意一个子进程结束,wait函数会返回结束的子进程的pid,结束信息保存在statloc指针指向的内存区域。如果该进程没有活动的子进程,那么立即出错并返回-1。如果statloc指针为NULL,那么表示不关心进程结束的状态。如果有多个子进程,wait函数返回哪个数不确定的,需要通过pid来判断。

如果我们需要等待特定的一个进程,可以使用pid_t waitpid(pid_t pid,int *statloc,int options)函数。waitpid函数的第一个参数指定了要等待的进程pid,并且有更多的选项。
僵尸进程

当一个子进程退出时,如果没有被父进程通过wait取得状态信息,这些信息会一直保留在内核内存中,子进程的pid也不会被消除,直到父进程退出,这时候这些子进程被称为僵尸进程(zombie process)。虽然僵尸进程只占用很少的一点内存,但如果是长期运行的服务器,积累大量的僵尸进程会导致系统进程表被塞满,以至于无法创建新的进程。产生一个僵尸进程很容易,只需要让子进程先于父进程退出即可,在父进程退出之前,子进程将会成为僵尸进程。
孤儿进程

与僵尸进程相反,如果父进程没有阻塞并先于子进程退出,那么子进程将会成为孤儿进程(orphan process)。Linux系统中init进程负责领养所有孤儿进程,也就是说,孤儿进程的父进程会被设为init进程。init进程作为系统守护进程(daemon process),会不断调用wait函数等待领养的孤儿进程退出,不会产生僵尸进程。
利用孤儿进程避免僵尸进程

许多时候我们不能让父进程阻塞下来等待子进程处理完以后再继续,例如在多用户的服务器程序上。这时如果让子进程处理事务,就会产生大量僵尸进程。避免僵尸进程出现的一个经典方法就是利用孤儿进程,具体方法为首先用父进程产生一个子进程,然后让子进程立刻产生一个孙子进程,用孙子进程来处理事务。同时父进程阻塞等待,并让子进程则立刻退出。这时候由于子进程已经退出,孙子进程就变成了孤儿进程,被init领养。而子进程立刻退出后,父进程收到信号并正确销毁了子进程,相比之下父进程只阻塞了可以忽略不计的一瞬间。下面程序是一个例子避免僵尸进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    if(fork() == 0) //启动一个子进程
    {
        printf("the child\n");
        if(fork() == 0) //启动一个孙子进程
        {
            printf("do something you want\n");
            sleep(5);
            printf("done\n");
            exit(0);
        }
        else //子进程立刻退出
            exit(0);
    }
    else
    { //父进程立即阻塞
        wait(NULL);
        printf("the parent\n");
        sleep(10);
    }
    return 0;
}

分享到:
评论

相关推荐

    进程代数入门教材

    ### 进程代数入门教材知识点详述 #### 一、进程代数概述 进程代数(Process Algebra)是一种数学框架,用于表达计算机系统的并发行为。它通过一组代数符号和运算来描述系统的交互过程,从而使得对复杂软件系统的行为...

    LabVIEW虚拟仪器控制入门手册

    "LabVIEW虚拟仪器控制入门手册"旨在帮助初学者理解并掌握如何利用LabVIEW进行虚拟仪器的设计和控制。本手册涵盖了LabVIEW的基本概念、编程界面、数据处理以及实际应用案例。 1. **LabVIEW基础概念**:LabVIEW的核心...

    C#零基础运动控制入门教学--低速连续运动与高速连续运动.zip

    本教程“C#零基础运动控制入门教学--低速连续运动与高速连续运动”旨在引导初学者掌握如何通过C#实现对设备或系统的低速连续运动和高速连续运动的精确控制。以下将详细解释相关知识点: 1. **C#语言基础**:C#是...

    linux进程操作

    linux进程控制, 入门操作

    易语言隐藏进程模块源码(1).rar

    3. **权限控制**:可能需要获取更高的系统权限(如SeDebugPrivilege),以允许隐藏高权限进程。 4. **处理进程信息**:可能涉及到获取和修改进程信息,如进程ID和父进程ID,以确保进程不被轻易识别。 学习和研究这...

    linux实验五_进程管理命令.pdf

    综上所述,本实验文档通过介绍top、ps、kill、fg、bg、jobs等进程管理命令,提供了对Linux进程管理操作的一个基本入门。这些命令的熟练掌握对于有效管理Linux系统来说至关重要,可以帮助系统管理员或用户更好地监控...

    易语言定时关闭一个进程

    在这个场景中,我们需要关注的是时间管理和进程控制相关的命令。易语言提供了“定时器”组件,可以用来设置定时任务。通过设定定时器的间隔时间,我们可以在到达特定时间点时触发一个事件,例如关闭某个进程。 在...

    精品课件 Python从入门到精通 第18章 使用进程和线程(共18页).pptx

    `Process`类允许我们指定启动时执行的可调用对象、进程别名、参数等,并提供了`is_alive()`、`join()`、`start()`、`run()`、`terminate()`等方法来管理和控制进程的生命周期。此外,`multiprocessing`模块还提供了...

    易语言源码易语言API进程应用源码.rar

    同时,对于有经验的开发者,这些源码也可能是解决问题的参考,比如遇到类似进程控制的难题时,可以借鉴其中的解决方案。 5. **调试技巧**:通过查看源码,可以学习如何在易语言环境下进行调试,理解程序的运行流程...

    易语言断开进程网络连接源码

    在IT行业中,网络连接是应用程序运行的重要组成部分,特别是在开发过程中,有时我们需要对进程进行网络控制,比如断开其网络连接。易语言,一种简洁而直观的编程语言,为初学者提供了方便的学习途径。本源码着重讲解...

    易语言远程进程加速变态

    在本主题中,易语言被用来编写代码,以控制远程进程并提升其执行效率。 远程进程加速通常涉及到以下步骤: 1. **进程通信**:首先,需要在本地计算机上编写程序,该程序能够与远程计算机上的目标进程建立连接。这...

    NGINX开发从入门到精通

    在操作NGINX时,我们可以通过向master进程发送信号来控制NGINX。例如,使用kill -HUP pid命令可以从容地重启NGINX。master进程在接收到HUP信号后,会重新加载配置文件,然后启动新的worker进程,并向所有老的worker...

    python入门教程(非常详细),从零基础入门到精通,看完这一篇就够了.pdf

    Python 入门教程详细知识点总结 本文档提供了 Python 编程语言的详细入门教程,从零基础到精通,涵盖了 Python 的各个方面,包括语法、数据类型、函数、模块、面向对象、异常处理、网络编程和并发编程等。 第一章...

    OPNET入门学习教程

    定义变量和宏是为了在进程模型中保存状态信息和控制逻辑。变量用于存储状态机内部的数据,例如数据包计数,而宏则用于简化条件判断和循环操作。宏在Proto-C中通过预处理指令#define来定义,它在预处理阶段被替换为...

    c-tutorial-main C语言入门教程 IO 进程内存线程并发

    C语言的基本结构包括变量、数据类型、运算符、控制结构(如if语句、switch语句、for循环、while循环等)。掌握这些基础知识是理解更复杂概念的关键。在"**c_tutorial-main**"中,你将学习如何声明和使用各种类型的...

    进程

    进程包括了程序代码、数据、堆栈空间以及进程控制块(PCB,Process Control Block)。PCB存储了关于进程状态(如就绪、运行、等待)、内存分配、I/O状态等关键信息,是操作系统管理和调度进程的核心数据结构。当操作...

    易语言检测进程是否存在源码

    在易语言中,通过特定的命令或函数,如“创建进程”、“查找进程”等,可以实现对进程的查询和控制。 描述中的"入门教程"提示我们,这个源码可能是针对初学者的教学示例,旨在教授如何利用易语言进行进程相关的操作...

    Python 进程互斥锁 Lock - Python零基础入门教程.pdf

    为了确保并发访问文件的安全性,我们可以使用`acquire`和`release`方法来控制资源访问。以下是一个简单的例子,模拟对全局变量的累加操作: ```python global_value = 0 def add_value(mutex): global global_...

Global site tag (gtag.js) - Google Analytics