Linux——进程管理

       进程和程序有本质的区别:程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程,它是程序执行和资源管理的最小单位。

进程描述符

       进程中不只是包含了程序指令和数据,还包含了进程运行的相关信息,如进程状态,打开的文件的描述符指针,这些都被包含在进程描述符中,其类型结构为(struct task_struct),而所有进程的进程描述符都被存放在叫做任务队列(task list)的双向循环链表中。

进程状态

       进程描述符中的state域表示进程的当前状态,该值为以下几种状态标志的值之一:

  • TASK_RUNNING(运行)——进程可执行,当前进程或者在执行,或者在运行队列中等待执行。这是进程在用户空间中执行的唯一可能状态。
  • TASK_INTERRUPTIBLE(可中断)——进程正在睡眠,直到获得满足它继续运行的条件。
  • TASK_UNINTERRUPTIBLE(不可中断)——在这个状态时进程也是停止运行,但是,即使获得满足它继续运行的条件,它也不会马上被唤醒。
  • TASK_TRACED(跟踪)——进程的执行由debugger程序暂停,当一个进程被另一个进程监控时,任何信号可以把这个进程置于TASK_TRACED状态。
  • TASK_STOPPED(停止)——进程停止运行,当进程收到SIGSTOP,SIGSTP,SIGTTIN,SIGTTOU等信号时就会停止。

       还有一种状态是既可以放在state域里面,也可以放在exit_state域里面

  • TASK_ZOMBIE(僵死)——进程运行结束,但是父进程还没有使用waitpid()或wait4()等系统调用来返回有关死亡进程的消息,所以内核在这个状态下没有销毁该死进程描述符里面的数据,而是等待父进程销毁它。

进程标识值

       Linux内核通过唯一的进程标志值PID来识别每一个进程。PID是一个非负短整型数,最大值为32767。可以通过修改/proc/sys/kernel/pid_max来提高上限。

进程控制API

进程创建

        先介绍一下Linux进程创建的一些特点:

        写时拷贝:传统的进程创建直接把所有的父进程资源复制给新创建的进程。这种方法不仅慢而且效率低。因此Linux的进程创建使用写时拷贝技术,此时并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝。只有在需要写入的时候才复制资源,使父子进程拥有各自的空间。

        Linux的进程创建主要由fork()和exec()两个函数实现。首先,fork()通过拷贝当前进程创建一个新进程exec()函数则负责读取可执行文件并将其之载入地址空间开始运行

fork

        其使用写时拷贝技术复制当前进程,创建子进程,当子进程被创建后,父子进程同时从fork()下句进入运行状态。但是两个进程中的fork返回值是不同的,父进程返回的是子进程的进程号,而子进程则返回0,如果返回-1,则出错。此时子进程与父进程的区别仅仅在于PID,PPID和一些资源和统计量

exec

        exec函数族提供了在一个进程中启动另一个进程执行的办法。它可以根据指定的路径和文件名找到可执行文件,并用它取代原调用进程的数据段、代码段和堆栈段。这里的可执行文件既可以是二进制文件,也可以是任何可执行的脚本文件。

        函数原型

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

       注意到可以传递可执行文件的文件名也可以给其完整路径,若只有文件名,系统会直接在环境变量PATH所包含的路径中查找。这些参数传递必须以NULL表示结束

       调用举例

/*相当于调用了"ls -l"命令*/
if(fork()==0)
{
    if(execlp("ls","ls","-1",NULL)<0)
        perror("execlp error!");
}
/*相当于调用了"ls -l"命令,给出了ls程序的完整路径*/
if(execlp("ls","ls","-1",NULL)<0)
    perror("execlp error!");

char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(execle("/bin/env","env",NULL,envp)<0)
    perror("execle error!");

char *arg[]={"env",NULL};
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(execle("/bin/env",arg,,envp)<0)
    perror("execve error!");
 
 
 
 
 

进程终止

       当一个进程终止时,内核必须释放它所占有的资源并告知其父进程它发生在进程调用exit()系统调用时,既可能显示地调用这个系统调用,也可能隐式地从某个程序的主函数返回(C语言编译器会在main()函数的返回点后面调用exit())在此之后,进程处于ZOMBIE状态,它占用的所有内存就是内核栈、thread_info结构和task_struct结构。此时就等待父进程检索到信息之后,或者通知内核那是无关的信息后,进程所持有的剩余内存被释放

进程删除

       父进程检索到信息后,或者通知内核那是无关的信息后,僵死进程所持有的剩余内存才会被释放这一过程就要依赖于wait()族函数。它的标准动作是挂起调用它的进程,直到其中一个子进程退出,此时函数会返回一个该子进程的PID。此外,调用该函数时提供的指针会包含子函数退出时的退出代码。

       函数原型:

pid_t wait(int *status);/*等待子进程结束,同时接受子进程退出时的状态*/
pid_t waitpid(pid_t pid,/*等待的子进程的类型*/
                     int *status,
                     int options);




孤儿进程

       如果父进程在子进程之前退出,那么就必须有机制来为子进程找到一个新的父进程,否则这些孤儿进程会在退出时永远处于僵死状态,耗费大量内存解决办法是为子进程在当前线程组内找一个线程作为父亲,如果不行,就让init作为它们的父进程这个会在do_exit中调用exit_notify(),该函数会调用forget_original_parent(),而后者会调用find_new_reaper()来执行寻父进程。

Linux中的线程实现

       从Linux内核的角度来说,并没有线程这个概念。所有的线程都当做进程来实现,只是这些线程是一些与其他进程共享一些资源的进程线程的创建和普通进程类似,其使用clone函数。

       如clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);上面的代码的结果与调用fork()差不多,只是父子进程共享地址空间、文件系统资源文件描述符和信号处理程序。而一个普通fork()的实现为:clone(SIGCHLD, 0);

       而vfork()的实现是:

       clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0);

       传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值