Unix环境_进程管理

一、基本概念
  1、进程与程序
  程序是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体。
  进程是一个正在运行的程序,一个程序可能包含多个进程(多任务、多进程),进程在操作系统中是一个执行任务的单位。

  2、进程的分类
  交互进程:需要用户输入数据,也会显示一些结果给用户看。
  批处理进程:用来执行脚本的进程,例如Makefile。
  守护进程:它是一种一直活跃的进程,一般是后台的,由操作系统启动时通过开过开机脚本加载或由超级用户加载。

  3、查看进程
  ps:显示当用户当前终端所控制的进程。
  -a:显示所有用户的进程
  -x:包括无终端控制的进程
  -u:显示详细信息
  -w:以更宽的方式显示

USER:属主  PID:进程号  %CPU:cpu占用率  %MEM:内存使用率  VSZ:虚拟内存的大小
RSS:物理内存的使用量  TTY:终端设备号,如果不是终端控制进程用'?'表示  STAT:终端的状态
START:开始时间  TIME:运行时间  COMMAND:开启此进程的命令
  
  4、进程的状态
  O:就绪态,一切准备工作都已经做好,等待被调用。
  R:运行态,Linux下没有就绪态,O也就是R。
  S:可唤醒的睡眠态,系统调用、获取到资源、收到信息都可以被唤醒。
  D:不可唤醒的睡眠态,必须等到的事件发生。
  T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
  X:死亡态。
  Z:僵尸态。
  <:高优先级。
  N:低优先级。
  L:多线程进程。
  s:有子进程的进程。
  +:后台进程组。

  5、父子进程
  如果进程B是由进程A开启的,那么我们把进程A叫进程B的父进程,进程B叫作进程A的子进程。
  当子进程结束后会向父进程发送,SIGCHLD,父进程收到信号后再回收子进程。
  当先父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
  如果子进程已经结束,但父进程没有及时回收,子进程就变成了僵尸进程。

  6、进程标识符,
  俗称进程号,它是一个无符号整数,使用getpid函数可以获取到。
  这种编号是循环使用,当进程结束后,它就跟这个编号没有关系,这个编号也会被再次使用,即延时重用。
  
二、getxxid
pid_t getpid(void);  功能:获取进程id

pid_t getppid(void);  功能:获取父进程id

uid_t getuid(void);  功能:获取实际调用者的用户id

uid_t geteuid(void);  功能:获取程序的拥有者用户的id

gid_t getgid(void);  功能:获取实际使用者的组id

gid_t getegid(void);  功能:获取程序的拥有者用户组id

三、fork
pid_t fork(void);
功能:创建子进程

1、失败返回-1,如果成功会返回两次。
2、父进程会返回子进程的id,子进程返回0
3、根据返回值的不同,分别为子进程和父进程设计不同的分支。
4、通过fork创建出的子进程,就是父进程的副本,它会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
5、fork函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但可以通过某些实现来保证。
6、当总进程数超过系统的限制时,就无法再创建进程的了,此时fork函数会返回-1,执行失败。
7、孤儿进程:子进程还有运行,但父进程已经结束,此时子进程会被init(1)收养,子进程变成孤儿进程。
8、僵尸进程:子进程已经死亡,但父进程没有及时回收子进程,此时子进程就会变成僵尸进程。
9、父进程打开的文件和子进程是共享的。
注意:fork之前的代码,只有父进程在执行,fork之后的代码父子进程都有机会执行,根据fork返回值来控制进入不同的分支。

#include <stdio.h>
#include <unistd.h>

int main()
{
    int pid = fork();
    if(0 == pid)
    {
           
        printf("进程id:%d 父进程id:%d
",
            getpid(),getppid());
        
    }
    sleep(1);
    printf("进程id:%d 子进程id:%d
",
        getpid(),pid);
}

执行结果为: 

 

四、vfork,execl
pid_t vfork(void);
功能:创建子进程

1、vfork不能单独创建子进程,需要与excl函数簇,配合才完成子进程的创建。
2、它不会复制父进程的栈、堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从面提高创建进程的效率。
3、使用vfork创建的子进程保证,先执行子进程,后执行父进程。

execl函数

int execl(const char *path, const char *arg, ...);

path:可执行文件的路径
arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可以执行文件名。
execl("","a.out",NULL);

五、进程的正常退出
1、从main退出,在main中执行return 0;

返回值的低8位会被父进程获取到。
使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托附给init,然后向父进程发送SIGCHLD信号。
此函数不会返回。

2、调用标准C的exit(stat)函数

exit在底层实现上调用了_exit/_Exit函数,所以_exit/_Exit的特点它都具备。
exit结束前会调用通过atexit/on_exit注册的函数。

int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);

3、最后一个线程正常结束

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void exit_func(int stats,void * str)
{
    printf("我是以%d的状态结束的,我最后遗言是:%s
",stats,str);
}

int main()
{
    on_exit(exit_func,"我走了");
    for(int i=0; i<5; i++)
    {
        sleep(1);
        printf("%d
",i++);
    }
    exit(110);
    return 1;
}

程序执行结果为:


六、进程异常中止
1、进程调用了abort函数(段错误、浮点异常)
2、进程接收到某些信号
crtl+c  crtl+  ctrl+z
3、最后一个线程收到取消操作,而且线程作出响应。

七、子进程的回收

pid_t wait(int *status);
功能:等待子进程结束,并回收

pid_t waitpid(pid_t pid, int *status,int options);
功能:等待指定的子进程结束,并回收

1、父进程调用wait

如果所有的子进程都在运行,则父进程阻塞(wait),只要有一个子进程结束了,会立即返回子进程的id和结束状态
当所有子进程都结束运行时,wait会返回-1。当不需要子进程的结束状态时,status的值可为NULL,可利用 while(-1 != wait(NULL)) 回收所有子进程。

2、waitpid函数可以指定等待哪个子进程结束
pid:指定的pid
  == -1 功能与wait类似,pid就无意义了。
  > 0 等待进程号是pid的进程结束。
  == 0 等待与父进程同组的字进程结束。
  < -1 等待组id是pid的绝对值的进程组中任意进程结束。

status:用于接收子进程的结束状态,如果不需要状态码可以设置为NULL;

options:
0 以阻塞状态等待子进程结束
WNOHANG 如果没有子进程退出会立即返回。
WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回。

3、如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会把他们统一回收。


原文地址:https://www.cnblogs.com/xiehuan-blog/p/9392667.html