本文主要是介绍linux 进程重要知识(详讲),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、进程基础
1.进程相关概念
进程是一个独立可调度的任务,进程是一个抽象的实体,当系统在执行某个程序时,分 配和释放的各种的资源
进程是一个程序的一次执行的过程
进程是程序执行资源管理的最小单位
进程和程序的区别:进程是动态的概念,它是程序执行的过程,包括创建、调度和消亡
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有 任何执行的概念
正文段(.test)
进程 用户数据段(.data) 程序
系统数据段
进程不仅包括程序的指令和数据,而且包括程序计数器值,CPU的所有寄存器以及存储临时数据的进程堆栈
2.linux下的进程结构
主要的进程标识:进程号(Process Identity Number , PID)
父进程号(Parent Process ID, PPID)
Linux中的进程包含三个段:
数据段:存放的是全局变量、常量以及动态数据分配的数据空间(如malloc函数取得的空间)等
正文段:存放的是程序中的代码
堆栈段:存放的是函数的返回地址、函数的参数以及程序中的局部变量
3.linux系统的进程类型
交互进程:该进程是由shell控制和运行的。交互进程既可以在前台使用,也可以在后台使用
批处理进程:该进程不属于某个终端,它被提交到一个队列中以便顺序执行
守护进程:该类进程实在后台运行,它一般在linux启动时开始执行,系统关闭时才结束。
4.进程运行状态
运行态(R):此时进程正在运行或者正在准备运行
等待态:此时进程正在等待某一个事件的发生或者某种系统资源(阻塞,休眠,等待,睡眠态)
可中断态(S):接收到信号有反应
不可中断态(D):接收到信号没有反应
停止态(T):此时进程已经被终止(SIGSTOP信号,SIGCONT信号)
死亡态(Z):这是一个已终止的进程,但还在进程向量数组汇总占有一个task_struct结构体(僵尸态)
进程状态图:
5.进程的模式
进程的执行模式分为用户模式和内核模式
在系统中,有一个定时器,每隔1ms产生一次中断,中断会进入内核(成为心跳,滴答tick);在用户模式与内核模式切换时,只有发生中断,才能从用户模式进入到内核模式,系统调用都是靠软件进行中断,
6.调度进程
ps 查看系统中的进程
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行的进程的优先级
kill 结束进程(包括后台进程)
bg 将挂起的进程在后台运行
fg 把后台的运行的进程放到前台运行
二、进程系统调用
1.进程创建:fock() 产生一个进程
头文件:#include<sys/types.h>//提供类型pid_t的定义 #include <unistd.h>
函数原型:pid_t fock(void);
函数返回值:0:子进程,从fock返回的地方开始执行
子进程PID(大于零的证书):父进程
-1:出错
getpid():获取pid号,子进程号
getppid():获取ppid号,父进程号
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。(系统启动首先要运行init进程,init扫描孤儿进程,收养孤儿进程)
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程,通常是由父进程清理子进程的僵尸。
2.exec函数族(替换一个进程的映像)
fock函数用于创建一个进程,该子进程几乎拷贝了父进程的全部内容
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或者目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
什么时候使用?
当进程认为自己不能在为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序。如果某个进程想同时执行另一个程序,它就可以调用fock函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。
#include <unistd.h>
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[]);
exec函数组使用区别:
可执行文件查找方式
参数表传递方式
环境变量的使用
exec函数族使用注意点:
在使用exec函数族时,一定要加上错误判断语句
常见的错误原因有:
找不到文件或者路径,此时errno被设置为ENOENT
数组argy和envp忘记用NULL结束,此时errno被设置为EFAULT
没有对应可执行的的运行权限,此时ERRNO被设置EACESS
3.exit 和 _exit
头文件: exit:#include<stdlib.h>
_exit: #include <unistd.h>
函数原型: exit: void exit(int status);
_exit:void _exit(int status);
函数传入值:status是一个证书的参数,可以利用这个参数传递进程结束时的状态,通常()表示正常结束;其他的数值表示出现了错误,进程非正常结束。在实际编程时,可以用wait系统调用接受子进程的返回值,进程相应的处理
_exit和exit的区别:
_exit()函数的作用最为简单:直接使进程终止结运行,清除其使用的内存空间,并销毁其在内核中的数据结构
exit()函数则在这些基础上做了一些包装,在执行退出之前加了若干道工序
exit()函数在调用exit系统调用之前要文件的打开情况,把文件缓冲区中的内容写入文件,就是图中的“清理I/O缓冲”一项
4.wait 和 waitpid
wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接受到了一个信号为止(等待态,可中断)。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
waitpid函数:功能和wait函数类似。不同的是可以指定某一子进程结束以及等待的方式(阻塞或非阻塞)
wait()函数
头文件 #include<sys/types.h>
#include <sys/wait.h>
函数原型: pid_t wait(int *status);
函数参数: status是一个整形指针,指向的对象用来保存子进程退出时的状态。
status若为空,表示忽略子进程退出时的状态
status若不为空,表示保存子进程退出时的状态
另外,子进程的结束状态可由linux中一些特定的宏来测定
waitpid()函数
头文件 #include<sys/types.h>
#include <sys/wait.h>
函数原型:pid_t waitpid(pid_t pid, int *status, int options)
函数参数:pid
pid>0:只等待进程ID等于pid的子进程,不管已经有其他子进程运行结 束退出了,只要指定的子进程还没 有结束,waitpid就会一直等下去
pid =1: 等待任何一个子进程退出,此时和wait作用一样
pid =0:等待其他组ID等于调用进程的组ID的任一子进程
pid<0: 等待七组ID等于pid的绝对值的任一进程
status:获取子进程结束信息
options:
WNOHOG,若由pid指定的子进程并不立即只使用,到waitpid不阻塞,此时返回值为0
WUNTRACED:若某实现支持作业控制,则由pid指定的任一子进程已暂停,且其状态自暂停以来还来报告过,则返回其状态
0:同wait,苏泽父进程,等待子进程退出
函数返回值:正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
调用出错:-1
5.linux守护进程
守护进程,也就是通常所说的Daemon进程,是linux中的后台服务进程,它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件
守护进程常常在系统启动时开始运行,在系统关闭时终止
linux系统有很多守护进程,大多数服务都是用守护进程实现的
在linux中,每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会被自动关闭。
守护进程能够突破这种限制,他从开始运行,直到整个系统关闭才会退出。如果让某个进程不会因为用户或者终端的变化而受到影响,就必须把这个进程变成一个守护进程。
6.linux守护进程编写步骤
(1) 创建子进程,父进程退出
第一步需要子进程在形式上做到与控制终端的脱离,由于父进程已经先于子进程退出, 子进程编程孤儿进程
pid=fork(); if(pid>0) {exit(0); }
(2)在子进程中创建新会话
进程组是一个或多个进程的集合。进程组由进程组ID来唯一表示,每个进程组都有一个组长进程,进程组ID就是组长进程的进程号;
会话期是一个或多个进程组的集合
setsid();
(3)改变当前目录为根目录
通常的做法是让“/”或“/tmp”作为守护进程的当前工作目录
在进程运行过程中,当前目录所在的文件系统时不能卸载的
chdir函数可以改变进程当前工作目录
chdir("/");
(4)重设文件权限掩码
文件权限掩码是指文件权限中被屏蔽掉的对应位把文件权限掩码设置为0,可以增加该守护进程的灵活性。
umask(0);
(5)关闭文件描述符
新建的子进程会从父进程那里继承所有已经打开的文件,在创建新的会话后,该守护进程已经脱离任何控制终端,应当关闭用不到的文件。(一般情况下只需要关闭系统自带的三个文件描述符就行),如果在这里需要打印调试,“先不脱离,关闭”。
close(0); close(1); close(2);
这篇关于linux 进程重要知识(详讲)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!