后台开发:Linux进程


前言:线程的可重入机制

       线程同步问题,本质都是为了解决“函数不可重入”的问题。可重入函数,指的是多于一个任务同时使用一个程序而不会出现数据错误。不可重入函数只能有同时一个程序使用,必须通过锁/信号量互斥的访问,或者在代码关键部分禁用中断。
       可重入函数可以在任意时刻中断,数据不会丢失,因此,可重入函数需要借助本地变量或者全局变量保护、保存自己的数据。

可重入函数的特点:
       1.不持有会被连续调用的静态数据。(售票系统的票数)
       2.不返回指向静态数据的指针(你无法知道你返回的数据会不会被其他进程修改)
       3.由函数调用者提供所有数据(也就是不利用静态数据,同1)
       4.使用本地数据或者全局数据的本地副本来保存数据,确保一次执行的结果与进程绑定。
       5.万不得已情况下,使用互斥锁,信号量访问静态数据或全局数据。
       6.不包含,不调用不可重入函数。

不可重入函数的特点:
       1.使用了静态变量,可能是全局静态变量或者全局静态变量。
       2.返回指向静态数据的指针或者静态变量。(你无法知道你返回的数据会不会被其他进程修改)
       3.调用不可重入函数
       4.使用了静态数据结构。
       5.内部调用了malloc和free函数。
       6.内部调用了其他IO函数。

       例如,多线程程序通过共享errno变量来报告出错代码,在一个线程准备获取刚才的错误代码时,该变量很容易被另 个线程中的函数调用所改变。
       多线程程序通过定义宏 REENTRANT 来告诉编译器需要可重人功能,这个宏的定义必须出现于程序中的任何#include 语句之前。
       例如,在 error.h 中定义的变量 error 现在将成为一个函数调用(类似get),它能够以一种安全的多线程方式来获取真正的 errno 的值;同时会把stdlib中的函数替换成可重入版本。

进程的组成

        数据段+堆栈段+代码段。
       多个运行统一程序的进程可以共享代码段。

程序–>进程

        1.内核将程序读入内存,为之分配空间。
       2.内核为进程分配进程标识符pid和堆栈等其他资源。
       3.内核保存进程状态和pid中,进程进入运行队列,成为可以被调度的进程。
       进程标识符本质上是一个无符号整数。
       进程只可以由父进程和操作系统创建。

       整个系统的进程创建流程:0号进程一> 1号内核进程一> 1号内核线程一> 1号用户进程( init进程)一>getty即进程一>shell 进程

进程创建过程

       操作系统启动时,创建进程目录树的根,0号进程,0号进程创建内核1号进程,1号进程处理系统配置和内核初始化;
       内核1号进程创建用于处理高速缓存和虚存管理的多个1号线程;内核1号进程调用execve的init函数,转变为1号用户进程init。
       1号用户进程创建若干用户线程getty用于注册终端,每个getty进程带有自己的进程组标识号和监听的端口。当收到端口的连接请求,调用login函数,当用户登录成功,调用execv函数执行shell,
       这个shell进程接受监听进程getty的pid,取代getty进程,并由shell进程创建新的用户进程。

进程创建

       pid_t fork(void);对于父进程,调用函数返回新建子进程PID,对于子进程,返回0。

进程退出

       void exit(int state),退出的状态保存在全局变量 $?中,可以通过echo $?获得上一个结束进程的退出状态。
$?的值可能是:
       如果main函数退出,保存main函数返回值;
       如果exit函数退出,保存exit函数的参数;
       如果异常退出,保存异常号。

正常退出

①在 main()函数中执行return;
②调用 exit()函数;
③调用_exit()函数;

异常退出

①调用 abort 函数;
②进程收到某个信号,而该信号使程序终止。

       不管是哪种退出方式,系统最终都会执行内核中的同一代码 这段代码用来关闭进程所用己打开的文件描述符,释放它所占用的内存和其他资源。

exit和return的区别

       exit 是一个函数,带有参数, exit 执行完后把控制权交给系统;
       return是函数执行完后的返回, return执行完后把控制权交给调用函数。

exit和_exit的区别

        exit() 是在头文 stdlib.h 中声明,而_exit()声明在头文件 unistd.h 中声明
       当程序执行到 exit 或_exit 时,系统无条件地停止剩下所有操作 ,清除包括 PCB 在内的各种数据结构,并终止本进程的运行。
        exit(0)表示正常终止,其他表示出错类型。
       _exit()执行后立即返回给内核,而 exit()要先执行一些清除操作,然后将控制权给内核。

        调用 exit 函数时,其会关闭进程所有的文件描述符,清理内存以及其他 些内核清理函数。
        exit 函数是在 exit 函数之上的个封装, 其会自动调用 exit ,并在调用之前先刷新流数据(stdin stdout stderr),把数据写入文件。

        exit函数与_exit函数最大区别在于: exit函数在调用 exit 系统之前要检查文件的打开情况,把缓冲区的内容写回文件。原因在于linux系统的缓冲IO操作,每次读文件会读入一批到缓冲区,写数据也是写入缓冲区,在特定时机写入真正的文件。因此,为了不流失流/缓冲区的数据,使用exit函数。

僵尸进程,孤儿进程

        孤儿进程是父进程已退出,而子进程未退出;子进程就无法退出,也无法回首自己的进程描述符和内存空间,产生的本质原因是父子进程时异步执行的,父进程不知道子进程的退出时机。
        僵尸进程:父进程未退出,而子进程已退出;父进程没有通过wait或者waitpid获取子进程的进程描述符等相关信息,子进程进程描述符保留在内存中。一个进程终止后,它的父进程需要调用wait或者waitpid函数来获取子进程中止状态。
        孤儿进程:父进程退出,子进程还在运行,成为孤儿。孤儿进程将被进程号为1的init进程收养,由init进程完成状态收集。

转换

       父进程休眠时,如果子进程退出,就会成为僵尸进程,当父进程被唤醒并退出后,这个将是进程会转化成孤儿进程,过继给init进程,init进程周期性执行wait系统调用,回收子进程标识符和内存空间。

wait函数

       pid_t wait(int* states);
       同样参数既然是一个指针,就可以做返回值。pid返回子进程标识符PID,states同时可以表示子进程结束状态值和进程标识符,通过二进制位表示。因此有两个专门的函数:
       WIFEXITED(states),检验进程是否正常退出,正常退出返回非0值。这个states不是wait函数的states参数,而是那个指针指向的整数;
       WEXITSTATUS(states),返回非0值表示进程退出的返回值,例如子进程exit(5),那这个函数就返回5。

waitpid

       在wait的基础上封装,可由用户控制pid和options参数。
       pid_t waitpid(pid_t pid,int* states,int options);
       这个函数同样会阻塞父进程,直到指定pid子进程结束,如果已经结束立刻返回进程结束状态值
       1.pid<=-1:取绝对值。
       2.pid=-1:等待任何子进程,等于wait。
       3.pid=0:等待进程组识别码和当前进程相同的任何子进程。
       4.pid>0:灯等待指定子进程。

       1.options=WNOHANG,不会阻塞父进程,没有子进程退出则立刻返回。
       2.options=WUNTRACED,子进程暂停时返回,而退出时反而不返回。
       可以通过|同时使用两个。
       返回值:
       1.正常返回:子进程PID。
       2.设置了WNOHANG,如果没有已退出的子进程,返回0;出错返回-1,同时设置全局变量errno的值为错误所在。
       例如,当pid指示的子进程不存在,或者或不是调用者的子进程,出错返回,errno=ECHILD。

守护进程

       负责操作系统中进行系统引导时会开启服务,这些服务进程就是守护进程。root可以选择系统的开启模式,这些模式叫做运行级别,每种运行模式下系统配置都不同。
       守护进程是脱离终端、运行于后台的进程。脱离终端的目的是不在任何终端上显示执行信息,也不会被任何终端产生的信息打断
       守护进程是生存期较长的进程,通常用于周期性执行某种任务或处理某些时间。守护进程一般在系统引导时启动,系统关闭时终止。系统服务一般通过守护进程实现,例如,作业规划进程crond,打印进程lqd。
       系统与用户交互的界面就是终端,在终端运行的进程会依附于这个终端,也就是这些进程的控制终端,终端被关闭,进程就终止。

创建守护进程的步骤

       1.创建子进程,父进程退出,形式上子进程脱离了终端,成为1好进城的的子进程。
       2.在子进程创建新会话。这里引出两个概念:进程组:一组进程,有唯一的进程组ID,每个组的组长进程PID等于进程组ID。进程组ID不受组长退出影响。会话周期:一个或多个进程组集合,用户登录期间创建的所有进程属于一个会话周期。
       setsid函数用于创建一个新会话,并担任会话组组长。
       这样做可以:1.让进程脱离原会话组。2.让进程脱离原进程组。3.让进程脱离原控制终端。因为这三个属性都在父进程fork的时候复制过来了,这里需要修改。
       3.设置当前目录为根目录
       fork创建的子进程会复制父进程的当前工作目录。因为进程运行时,是不能卸载当前目录所在的文件系统的,因此,让“/”作为守护进程的当前工作目录,或者其他包含守护进程服务范围的目录。
       4.重置文件权限掩码
       文件权限掩码会屏蔽掉文件权限的对应位。例如,050屏蔽文件组拥有者的读和执行权限(r,读权限,read,4;w,写权限,write,2;x,操作权限,execute ,1)。这个也是因为fork复制过来的。一般用unmask(0)。
       5.关闭文件描述符
       关闭父进程fork过来的,已经打开的文件描述符。避免不会调用而打开文件的资源浪费,甚至导致文件系统无法正常退出。此时,0,1,2号文件也失去了价值(输入输出错误输出)。一般用循环(1…MAXFILE)来关闭所有文件描述符。
       创建的守护进程要通过ps -ef |grep test 来查看,不需要的时候kill grep显示的进程号就行了。

结语

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰的野望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值