博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux下进程的一些总结
阅读量:2441 次
发布时间:2019-05-10

本文共 4241 字,大约阅读时间需要 14 分钟。

      引用《linux程序设计》的一句话:进程和信号构成了Linux操作环境的基础部分,控制着Linux和其他所有类UNIX计算机系统执行的几乎所有动作。(哇,这么高端, 其实算来,windows下,貌似也是进程,不过信号有没有这么重要的作用就不知道了,不过windows下应该是消息)

 一、什么是进程

      说到进程,其实在操作系统这门课就讲过,也就是正在执行的程序(当然,这定义太简单了)。UNIX标准将进程定义为:一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。目前,先将进程看做正在运行的程序。

       需要说的是,Linux高效性的一个地方在于,系统会在进程间共享程序代码和系统函数库,所以在任一时刻,内存中都只有代码的一份副本

二、进程的管理

        如果有2个用户neil和rick,他们同时运行grep在不同文件中查找不同字符串,则使用的进程如下图所示:

       

        进程的关键要素,PID这个直接调用getpid()就可以得到,取值2到32768(1呢?给init进程了啊)

        正常情况下,进程不能对用来存放代码的内存区域进行写操作,即代码是以只读方式加载到内存的。虽然不能写,不过可以被多个进程安全的共享。可以共享的不知是用户函数,系统函数也可以,共享的思想,类似于windows下的.dll机制。不过不是所有东西都可以共享,进程的变量就不行。(其实说到共享,想到在unix环境下,进程通信貌似是很重要的部分呢么)

         另外,进程还有自己的栈空间,用于保存函数中的局部变量和控制函数的调用与返回。进程还有自己的环境空间,包含专门为这个进程建立的环境变量(其实对于这个不是很理解)

三、启动进程以及linux的启动

        启动进程,我们可以用system函数(其实以前看过还可以fork什么的,后面上)。system原型为:

#include 
int system(const char *string);
      以前经常用system("cls")和pause什么的,system的作用就是运行以字符串参数的形式传递给它的命令并等待该命令的完成。

      linux的启动过程呢,可以参考,不过这个略高深了点。单从进程的启动过程来讲,启动过程就是,首先启动init进程(pid=1)可以把它看做整个系统的进程管理器,或者看做祖先进程。然后init来处理系统的初始化,(主要是rc.sysinit)处理完后,回到init,启动系统服务和设置文件。下面终于到我们熟悉的终端和x-window界面了。(可参考:)

四、exec(),fork(),wait()僵尸进程

        突然想起,在kismet的源代码中见过exec()和fork(),不过对这俩印象不是很深刻,下面来学习一下吧。

1、  exec():替换进程映像。exec家族函数:

#include 
char **environ;int execl(const char *path, const char *arg0, ... ,(char *)0);int execlp(const char *file, const char *arg0, ...,(char *)0);int execle(const char *path, const char *arg, ...,(char *)0,char *const envp[]) ;int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);
        这些函数可以分为2大类。execl、execlp和execle的参数个数可变,参数以一个空指针结束;execv和execvp的第二个参数是一个字符串数组(和main的命令行参数一样)。不管哪种情况,新程序启动时会把argv数组中的参数传递给main函数。

        p结尾的函数是通过搜索PATH环境变量来查找新程序的可执行文件的路径,如可执行文件不在PATH定义路径中,我们就需要把包括目录在内的使用绝对路径的文件名作为参数传递。

         environ为全局变量,可用来把一个值传递到新的程序环境中。此外,函数execle和execve可以通过envp传递字符串数组作为新程序的环境变量。         

#include 
#include
#include
int main(){ printf("Running ps with execlp \n"); execlp("ps","ps","ax",0); printf("Done .\n"); exit(0);}
    运行以上程序,有正常的ps输出,却没有done的输出,另外,ps的输出中,没有exec进程(本程序可执行名)的任何信息。
2、fork():复制进程映像

       关于fork(),有提到,不过个人更喜欢,毕竟,fork出来的进程,说父子程序,其实也可以说兄弟程序。fork过程示意图:

      fork()函数原型:

#include 
#include
pid_t fork(void);
     关于fork,需要注意的几个点就是:

     a.fork是对当前父进程的复制,包括代码段,数据段,堆栈段。说是复制,其实这里用共享更合适,如所讲,fork对于父子进程的划分,只是逻辑上,不是物理上,只有数据改变时,才物理上进行划分。不过不精确,因为共享的,显然只能是代码段,数据必然是进程所独有的,堆栈也只能的记录fork那个时刻的堆栈情况。所以虽然子进程继承了父进程的大部分资源,不过只是父进程在fork那个时刻的数据,在fork以后,数据段就分开了,要进行共享,只能进程通信实现。

     b.父子进程的pid,对子进程是0,对父进程在而是一个正整数。

     示范程序如下:

#include 
#include
#include
#include
int main(){ pid_t pid; char *message; int n; printf("fork program starting ...\n"); pid = fork(); switch (pid) { case -1: perror("fork failed!"); exit(1); case 0: message = "This is the child "; n = 5; break; default: message = "This is the parent" ; n = 3 ; break; } for ( ; n > 0 ; i-- ) { puts(message); sleep(1); } exit(0);}
       这个程序以2个进程的形式存在,子进程被创建并输出5次,父进程只输出3次。父进程在打印完它的全部消息后接受,因此我们能看到在输出内容中混杂了一个shell提示符(从这还是能看出是父子关系啊)结果如下:

3、wait():等待一个进程

      上一个示例,父进程在子进程结束之前结束了,不过子进程还在继续运行,得到的输出就有点乱了,我们可以通过在父进程中调用wait函数让父进程来等待子进程的结束。          wait()函数原型如下:

#include 
#include
pid_t wait(int *stat_loc);
       wait调用的作用,暂停父进程直到它的子进程结束为止,调用返回子进程的pid,通常是已经结束的子进程的pid。对上面fork的例子,我们可以加上如下一段来等待子进程的完成。
if (pid != 0)	{		int stat_val;		pid_t child_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 terminated abnormally \n");	}
运行结果如下:

     在上面的代码中,父进程用wait调用将自己的执行挂起,直到子进程的状态信息出现为止(在子进程调用exit时发生)。

4、僵尸进程

      用fork产生进程时,还有一个问题,子进程终止时,它与父进程之间的关联还会保持,直到父进程也正常终止或父进程调用wait才结束。因此,进程表中代表子进程的表项不会立即释放,虽然子进程已不再运行,但它仍存在于系统中,因为它的退出码还需要保存起来,以备父进程今后的wait调用,这时候,它就成为了一个僵尸zombie进程。

     对上面的fork示例,我们兑换父子进程的n值,就可以看到僵尸进程了。(在子进程结束后,父进程结束之前调用ps,我们会看到<defunct>或者<zombie>)lz的f9看到的是 如下情况:

      如果此时父进程异常终止,子进程自动把PID为1的进程(即init)作为自己的父进程。这样,如果异常终止的过多,init接管的就越多,僵尸进程一直保留在进程表中,直到被init发现并释放。表越大,过程就越慢,所以应该尽量避免僵尸进程产生。

       僵尸进程的回收,可以用wait来实现,具体可以参考:

      另外,里还有exit和_exit(),看到这个就想起了cd和cd ~,su和su -的区别(虽然在f9上还是有区别的,不过在ubuntu10.10上看不出任何差别啊,可以看做系统更人性化了吧)

       目前就是这样了,对于进程什么的,还是压力巨大的,对window下的线程倒是试过,以后有时间了,慢慢试试linux下的进程通信什么的。

       菜鸟goes on~~~

转载地址:http://eocqb.baihongyu.com/

你可能感兴趣的文章
rxjs 搜索_如何使用RxJS构建搜索栏
查看>>
如何在Debian 10上安装MariaDB
查看>>
go函数的可变长参数_如何在Go中使用可变参数函数
查看>>
react开源_React Icons让您可以访问数百个开源图标
查看>>
debian 服务器_使用Debian 10进行初始服务器设置
查看>>
joi 参数验证_使用Joi进行节点API架构验证
查看>>
react-notifications-component,一个强大的React Notifications库
查看>>
如何在Debian 10上设置SSH密钥
查看>>
如何在Debian 10上安装Node.js
查看>>
了解css_了解CSS的特异性
查看>>
emmet快速插入css_如何使用Emmet快速编写HTML
查看>>
graphql_GraphQL简介
查看>>
typescript 枚举_TypeScript枚举声明和合并
查看>>
flutter开发_加快Flutter开发的提示
查看>>
redis排序_如何在Redis中管理排序集
查看>>
使用Gatsby和Cosmic JS创建多语言网站
查看>>
redis 连接数据库_如何连接到Redis数据库
查看>>
如何在Ubuntu 18.04上对Redis服务器的性能进行基准测试
查看>>
如何在Ubuntu 18.04上安装Nginx
查看>>
如何在Ubuntu 20.04上安装Nginx
查看>>