进程

一个程序是存储在文件中的机器指令序列,一般它是由编译器将源代码编译成二进制格式的代码。运行一个程序意味着将这个机器指令序列载入内存然后让处理器(cpu)逐条执行这些指令。一个可执行程序是一个机器指令机器及其数据的序列,一个进程是程序运行时的内存空间和设置。

利用 ps -a查看进程,S列的值为R说明ps对应的进程正在运行。S列值是S说明进程正在睡眠状态,标记为PRI和NI的列分别是进程的优先级和niceness级别,SZ列表示进程占用内存的大小。WCHAN列显示进程睡眠的原因,常见的系统进程:内核缓冲、虚存页面。管理系统日志、调度批任务、防范可能的攻击和让一般的用户登录。

内存可以看作是一个容纳内核和进程的空间,很多系统把内存着作由页面构成的数组,将进程分割到不同的页面.物理上,这些页面可
能被存放在固体的芯片中 .UNIX系统中的内存分为系统空间和用户空间。进程存在于用户空间,内存实际上就是一个字节序列,或者一个很大的数组。如果机器有64MB的内存,那就意味着这个数组大约有6700万个内存位置,其中的一些用来存放组成内核的机器指令和数据。还有一些存放组成进程的机器指令和数据。一个进程不一定占据一段连续的内存(类似于文件在磁盘上被分成小块,进程在内存也被分为小块)。同样和文件有记录分配了的磁盘块的列表相似,进程也有保存分配到的内存页面的数据结构,因此将进程表示为用户空间内的一个小方块只是某种程度的抽象。将内存表示为连续的字节序列也是一种抽象。 

一个程序可以调用exec函数簇运行另一个程序:

   execvp(progname,arglist);

execv(),execvp()和execvpe()函数提供了一个指向null结尾字符串的指针数组,这些字符串表示新程序可用的参数列表。按照惯例,第一个参数应指向与正在执行的文件相关的文件名。 指针数组必须由空指针终止。

   a、将指定的程序复制到调用它的进程

   b、将指定的字符串数组作为argv[]传给这个程序

   c、运行这个程序

   具体步骤:

   (1)程序调用execvp

   (2)内核从磁盘奖程序载入

   (3)内核将arglist复制到进程

   (4)内核调用main(argc,argv);

execvp由两个参数:要运行的程序名和那个程序的命令行参数数组,当程序运行时命令行参数以argv[ ]传递给程序(数组的第一个元素为程序名字,最后一个元素为null)。

如果直接在主进程中执行execvp函数,内核会将execvp要执行的程序载入到主进程,替代主进程的代码和数据。

exec系统调用从当前进程中把当前程序的机器指令清除,然后在空的进程中载入调用时指定的程序代码,最后运行这个新程序。exec调整进程的内存分配使之适应新的程序对内存的要求,也就是说进程是相同的,内容是不同的。



execvp载入由file指定的程序到当前的进程,然后试图运行它,execvp将以NULL结尾的字符串列表传给程序,execvp在环境变量PATH所指定的路径中查找file文件,

如果执行成功,execvp没有返回值,当前进程从进程中清除,新的程序在当前进程中执行。

fork():fork()通过复制调用过程来创建一个新的过程。新进程被称为子进程。 调用过程被称为父进程。子进程和父进程在单独的内存空间中运行。 在fork()时,两个内存空间都有相同的内容。 由其中一个进程执行的内存写入,文件映射(mmap(2))和取消映射(munmap(2))不会影响另一个进程。

进程拥有程序和当前运行到的位置,进程调用fork,当控制转移到内核中的fork代码后,内核做:

 (1)分配新的内存块和内核数据结构

 (2)复制原来的进程到新的进程

 (3)向运行进程集添加新的进程

 (4)将控制返回给两个进程

内核通过复制父进程来创建子进程,它将父进程的代码和当前运行到的位置都复制给子进程,子进程从fork返回的地方开始运行,

父进程调用wait等待子进程退出。系统调用wait做两件事,首先wait暂停(阻塞)调用它的进程直到子进程结束,然后wait取得子进程结束时传给exit的值,当子进程调用exit,内核唤醒父进程同时将子进程传给exit参数,wait执行两个操作,通知和通信。

pid_t wait(int *status);

wait的目的之一是通知父进程子进程结束了,它的第二个目的是告诉父进程子进程是如何结束的,一个进程以3种方式(成功、失败、或死亡)之一结束,其一,一个进程可能顺利完成它的任务,按照惯例,成功的程序调用exit(0)或者从main函数中return 0;

其二,进程可能失败,比如进程可能由于内存耗尽而提前退出程序,按照惯例,程序遇到问题而要退出调用exit时传给它一个非零的值,程序员可以对不同的错误分配不同的值。最后程序可能被一个信号杀死,信号可能来自键盘(^C^\)、间隔计时器,内核或者其他进程,通常情况下,一个既没有被忽略又没有被捕获信号的信号会杀死进程,wait返回结束的子进程的id给父进程,父进程调用wait时传一个整形变量地址给函数,内核将子进程的退出状态保存在这个变量中,如果进程是被杀死的,那么信号将序列存放在这个变量中,这个整数由三部分组成,8个bit是记录的退出值,7个bit是记录的信号序列,另一个bit用来指明发生错误并产生了内核映像,




wait系统函数挂起调用它的进程直到得到这个进程的子进程的一个结束状态,结束状态是退出值或者是信号序列,如果一个子进程已经退出或被杀死,对wait的调用立即返回,wait返回结束进程的id,如果statusptr不是NULL,wait将退出状态或者信号序号,复制到statusptr指向的整数中,这个值可以用<sys/wait.h>中的宏来检测,如果调用的进程没有子进程也没有得到终止状态值,则wait返回-1.

键盘信号发送给所有连接的进程。

由exec传递的参数必须是字符串。由于进程间通信的参数类型是字符串,这就强迫了子程序的通信也必须使用文本作为参数类型。几乎是偶然的,这种基于文本的程序接口支持跨平台的交互,而这一点是非常重要的。

全局变量是有害的,它破坏了封装原则,导致出人意料的副作用和难以维护的代码,但由时候去掉全局变量更糟糕,UNIX提供方法来建立全局变量。环境是一些传递给进程的字符串型变量集合,不会副作用,它对fork/exec和exit/wait机制是一个有用的补充。

exit是fork的逆操作,进程调用exit来停止运行,fork创建一个进程,exit删除进程,exit刷新所有的流。调用由atexit和on_exit注册的函数,执行当前系统定义的其他与exit相关的操作,然后调用_exit。系统函数_exit是一个内核操作,这个操作处理所有分配给进程的内存,关闭所有这个进程打开的文件,释放所有内核用来管理和维护这个进程的数据结构,那个进程退出所返回的信息被存放在内核直到进程的父进程通过wait系统调用取回这个值,如果父进程没有在等这个值,那么他将被保存在内核直到父进程调用wait,那时内核将通告这个父进程子进程的结束,并转达子进程的退出时返回的信息,那些已经死亡但是还没有给exit赋值的进程被称之为幽灵进程,很多比较新的版本的ps列出了这些进程并标记为defunct.

_exit小结:

系统调用_exit终止当前进程并执行所有必须的清理工作,这些工作在各个不同版本的unix中有些不同,但都包括以下操作:

 (1)关闭所有文件描述符和目录描述符

 (2)将该进程的PID置为init进程的PID

 (3)如果父进程调用wait或waitpid来等待子进程结束,则通知父进程

(4)向父进程发送SIGCHLD(子进程结束或停止)

这样,如果父进程在子进程之前退出,那么子进程将能继续运行,而不会称为“孤儿进程”它们将是init进程的子进程,就算父进程没有调用wait,内核也会向它发送SIGCHLD,但是对SIGCHLD消息的默认处理方式是忽略。

execvp不是一个系统调用,它是一个库函数,这个函数通过系统调用execve来调用内核服务,execve中的e代表环境

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

execlp传给main的argv中包括的参数被简单的放在execlp的参数中,当预先知道要运行的命令和它的参数时execlp是有用的,execlp和execvp中的p代表路径,这两个函数在环境变量PATH中列出的路径中查找由第一个参数指定的程序,如果准确知道这个文件的位置,那么就能够在execl中的第一个参数中指定它的完整路径(速度快,安全)。execv和execvp除了必须指定具体位置之外非常相似。

进程结构:

进程就是相对于进程被创建时候调用创建函数的进程,子进程就是一个进程调用了创建函数而创建出来的进程。

孤儿进程就是父进程已经注销,但是子进程还在的进程。


fork进程时,操作系统会给新的进程分配资源,并且将父进程的工作空间复制进子进程


守护进程是一类在后台运行的特殊进程,用于执行特定的系统任务,很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭,另一些只在需要的时候才启动,完成任务后自动结束。


当进程已经停止运行,父进程没有关注子进程的状态,这种进程是僵尸进程。

危害:僵尸进程会占用系统资源,使那些被占据的资源不可以使用

避免:父进程通过waitwaitpid等函数等待子进程结束,这回导致父进程挂起。如果父进程很忙,可以用signal函数微SIGCHLD安装handler,因为子进程结束后,父进程收到该信号,可以在handler中调用wait回收

如果父进程不关心子进程什么时候结束,可以使用signalSIGCHLDSIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并且不再给父进程发送信号。利用子进程fork孙进程,让子进程退出,使孙进程变成孤儿进程,就不会担心僵尸进程了。


一个父进程有多个子进程不能通过哪个系统调用的到所有的子进程Id,而每个子进程只有一个父进程可以很容易得到父进程id

就绪,运行,阻塞。就绪到运行是所有需要资源都准备好,只需要cpu来运行

就绪要运行是,时间片到了。

运行到阻塞,是进程所需i/o等资源被别的进程占用申请不到

阻塞到就绪,进程所需资源一旦可以申请到,就转换就转换为就绪太。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值