目录
一、进程基础
1.进程相关基本概念
2.进程与程序
3.Linux下的进程结构
4.Linux系统中的进程类型
5.进程的运行状态
6.进程的执行模式
7.用户模式于内核模式的切换
8.Linux下的进程管理
9.调度进程相关命令
二、进程系统调用
1.进程的优先级
2.前后台进程切换
三、进程相关接口函数
1.创建子进程 -- fork()
2.结束进程 -- exit() _exit()
3、进程回收 -- wait 、waitpid
4.exec函数族
1.为什么?
2.怎么做?
四、守护进程
守护进程相关概念
创建守护进程
守护进程创建例子
一、进程基础
1.进程相关基本概念
1)进程是一个独立的可调度的任务
进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
2)进程是一个程序的一次执行的过程
3)进程和程序的区别
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡;
4)进程是程序执行和资源管理的最小单位
2.进程与程序
2.进程与程序
进程不仅包括程序的指令和数据,而且包括程序计数器值、CPU的所有寄存器值以及存储临时数据的进程堆栈。
系统数据段又包含(PCB 进程控制块 , pc(程序计数器),堆栈)
PCB:
进程ID
用户名、组名
进程的状态、优先级
文件描述符表(记录打开的文件--用文件描述符标识)
这里先注意一个概念(时间片)
时间片:系统暂时允许(分配)程序运行的时间 看优先级
PC(程序寄存器):
记录程序下一条指令的地址
3.Linux下的进程结构
1)主要的进程标识
进程号(Process Identity Number,PID)
父进程号(Parent Process ID,PPID)
2)********PID唯一地标识一个进程********
3)Linux中的进程包含三个段
“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空 间)等。
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
4.Linux系统中的进程类型
交互进程
:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也 可以在后台运行。
批处理进程 :该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。一般由系统管理员
操作; 守护进程 :该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束 。
5.进程的运行状态
1)R(run) -- 运行态(就绪态): 正在运行或者准备运行的进程
2)等待态:两种等待态区别在于是否能被信号(对进程的操作指令 例如:kill -9)打断
S(sleep) -- 可中断等待态 :等待某种资源,有资源之后继续执行(相当于原地踏步)
D(deepsleep) -- 不可中断等待态
3)T(time-out) -- 暂停态 :暂停运行,直到有信号唤醒位置
4)Z(zombie) -- 僵尸态 :进程结束之后,没有进行资源回收,该进程状态变为僵尸态
6.进程的执行模式 进程的执行模式分为用户模式和内核模式
7.用户模式于内核模式的切换
8.Linux下的进程管理
启动进程:
手工启动:
由用户输入命令直接启动进程
前台运行和后台运行
调度启动:
系统根据用户事先的设定自行启动进程
at 在指定时刻执行相关进程,
cron 周期性执行相关进程
9.调度进程相关命令
ps 查看系统中的进程 ----- ps -ef ----- ps -aux
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
kill 向进程发信号
bg 将挂起的进程在后台执行
fg 把后台运行的进程放到前台运行
二、进程系统调用
二、进程系统调用
1.进程的优先级
进程优先级取值范围: -20 ~ 19 默认值为零(值越小优先级越高) 默认值为0
nice -n 2 ./a.out 在运行前,将a.out的优先等级设为2
renice -n 2 进程号 //在运行时修改
2.前后台进程切换
/a.out+& (后台运行进程)
kill -9 +进程号(杀死后台进程)
bg + 任务号 :将暂停的程序放在后台继续运行
fg + 任务号 :将后台运行的进程换到前台进行
jobs : 查看后台任务
三、进程相关接口函数
1.创建子进程 -- fork()
#include//所需头文件 #include pid_t fork(void);//函数原型 返回值: 成功创建一个新的子进程 1)父进程返回子进程的PID号 2)子进程返回0 失败父进程返回-1,没有子进程被创建
注:你没有看错这个函数在子进程创建成功时有两个返回值(就看你要接收谁)
举两个栗子:
#include#include #include #include int main() { pid_t pid; if ((pid = fork()) = = -1) { perror("fork"); return -1; }else if (pid = = 0){ printf("The return value is %d In child process!! My PID is %d, My PPID is %dn",pid,getpid(), getppid()); } else { printf("The return value is %d In parent process!! My PID is %d, My PPID is %dn",pid,getpid(), getppid()); } return 0; }
#include这里列出几个父子进程需要注意的点:#include #include #include int main(int argc, char *argv[]) { int a = 2; pid_t pid = fork();//接收函数返回值 if(pid < 0)//小于零时则创建失败 { perror("fork");//打印错误原因 return -1; } if(pid == 0)//等于零表示创建子程序成功且这个if里面的所有语句是子进程执行 { int n = 10; while(n--) { if(n == 4) { exit(0);//这个是结束进程函数,之后有讲解 } printf("bbbbbbbbbbbb child_pid = %d a = %dn", getpid(), a); sleep(1); } } else //这里就是>0的情况且代表父进程 { int n = 3; while(1) { printf("aaaaaaaaaaa parent_pid = %d a = %dn", getpid(), a); sleep(1); } } return 0; }
1)一个进程通过fork函数创建一个新的进程,原本进程称为新进程的父进程,新的进程称为原进
程的子进程子进程会继承父进程中几乎所有数据(包括局部变量、文件标识符) 。
2)如果父进程优先于子进程结束:
子进程称为孤儿进程,由前台进程变为后台进程,统一由init进程(系统默认进程)收养;
3)如果子进程先于父进程结束,且父进程没有回收子进程资源;子进程变成僵尸进程(僵尸态)
一般来说,如果子进程先于父进程结束,子进程应该统一由父进程回收
***子进程在fork语句的下一条指令开始执行
2.结束进程 -- exit() _exit()
#include//所需头文件 void exit(int status);//函数原型 参数: status :表示进程退出的状态 正常结束 异常结束 例如:(kill -9)
#includevoid _exit(int status); 注意:exit函数调用后会刷新所有缓冲区,_exit函数不会刷新
注:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。 通常0表示正常结束;其他的数值表示出现了错误,进程非正常结束。 在实际编程时,可以用wait系统调用接收子进程的返回值,进行相应的 处理。
exit 和 _exit 之间的区别:
1)_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间, 并销毁其在内核中的各种数据结构; 2)exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。 3)exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的 内容写回文件,就是图中的"清理I/O缓冲"一项。eg:
#includeint main() { printf("this process will exit!"); exit(0); printf("never be displayed!"); }
3、进程回收 -- wait 、waitpid
wait函数
调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程
没有子进程或者其子进程已经结束,wait函数会立即返回。
waitpid函数
功能和wait函数类似;可以指定(通过进程号)等待某个子进程结束以及等待的方式(阻塞或非阻塞)
#include
#include
pid_t wait(int *wstatus);
参数:
wstatus:进程结束时,状态信息的首地址
返回值:
成功返回结束子进程的pid号,失败返回-1
如果想要得到子进程结束的状态信息,可以用以下宏来得到:
WIFEXITED(wstatus) -- 判断一个子进程是否是正常退出,正常退出为真,非正常退出为假
WEXITSTATUS(wstatus) -- 返回子进程结束的返回值
WIFSIGNALED(wstatus) -- 判断是否被信号终止
WTERMSIG(wstatus) -- 打印终止进程信号的编号
当然还有其他的宏,可以通过man手册查看
----------------------------------------------------------------------------------
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid:进程号, -1表示接收任意子进程
wstatus:进程结束时,状态信息的首地址
options:
0 -- 以阻塞方式等待子进程结束
WNOHANG -- 以非阻塞方式等待子进程结束
eg:
#include#include #include #include #include int main(int argc, char *argv[]) { int a = 2; pid_t pid = fork(); if(pid < 0) { perror("fork"); return -1; } if(pid == 0) { int n = 20; while(n--) { printf("bbbbbbbbbbbb child_pid = %d a = %dn", getpid(), a); sleep(1); } exit(99); } else { int ret = -1; int status; int n = 3; //pid = wait(&status); ret = waitpid(pid, &status, 0); if(ret < 0) { perror("wait"); exit(-1); } else { printf("pid = %dn", pid); } printf("%d %d %d %d n", WIFEXITED(status), WEXITSTATUS(status), WIFSIGNALED(status), WTERMSIG(status)); } return 0; }
4.exec函数族
1.为什么?
fork函数用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容。exec函数族提供了一种在
进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来
取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其
他全部都被替换了。
注:可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
2.怎么做?
当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任
何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样;
#include
l:list列表
v: argv
p: PATH环境变量
int execl(const char *pathname, const char *arg, ...);
参数:
pathname:执行程序的文件名(包含路径)//默认当前路径
arg:执行程序的命令行参数,命令行参数列表以NULL结尾
返回值:
失败返回-1;
-----------------------------------------------------------------------------
int execlp(const char *file, const char *arg, ...);
参数:
file:程序名
添加环境变量:
在~/.bashrc中添加命令:
export PATH=$PATH:你要操作进程的绝对路径
配置完成之后,需要用以下指令让配置生效:
source ~/.bashrc
--------------------------------------------------------------------------------
//注意以下两个函数,用法都跟上面的函数差不多,只是要注意这里的形参有一个指针数组;
//其实就是把你要操作的进程用一个数组保存,上面的函数只是将这些进程一一列举罢了;
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
2.怎么做?
当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任
何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样;
#include
l:list列表
v: argv
p: PATH环境变量
int execl(const char *pathname, const char *arg, ...);
参数:
pathname:执行程序的文件名(包含路径)//默认当前路径
arg:执行程序的命令行参数,命令行参数列表以NULL结尾
返回值:
失败返回-1;
-----------------------------------------------------------------------------
int execlp(const char *file, const char *arg, ...);
参数:
file:程序名
添加环境变量:
在~/.bashrc中添加命令:
export PATH=$PATH:你要操作进程的绝对路径
配置完成之后,需要用以下指令让配置生效:
source ~/.bashrc
--------------------------------------------------------------------------------
//注意以下两个函数,用法都跟上面的函数差不多,只是要注意这里的形参有一个指针数组;
//其实就是把你要操作的进程用一个数组保存,上面的函数只是将这些进程一一列举罢了;
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
这几组函数加不加P和加P的区别就在于是否默认操作当前路径下的进程,或者是否要改变环境变量
exec函数族使用区别 可执行文件查找方式 表中的前四个函数的查找方式都是指定完整的文件目录路径,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。 参数表传递方式 两种方式:逐个列举或是将所有参数通过指针数组传递以函数名的第五位字母来区分,字母为“l”(list)的表示逐个列举的方式;字母为“v”(vertor)的表示将所有参数构造成指针数组传递,其语法为char *const argv[] 环境变量的使用 exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量
四、守护进程
守护进程相关概念
守护进程跟终端无关,负责在后台周期性的处理某些事件或者等待某些事件响应;
1)进程组:
当用户执行一个程序(进程)时,就相当于创建了一个进程组,跟该进程具
有亲缘关系的所有进程都属于该进程组;
2)会话:
当用户打开一个终端时,就创建了一个会话,一个会话由一个或者多个进程
组成,一旦终端关闭,该会话中所有进程组中的进程全部结束;
创建守护进程
守护进程的创建流程: 1.创建子进程,父进程退出 fork(); 2.让子进程脱离原本会话 setsid(); setsid函数作用: setsid函数用于创建一个新的会话,并使得当前进程成为新会话组的组长 setsid函数能够使进程完全独立出来,从 而脱离所有其他进程的控制。 3.修改当前工作路径 -- 非必要 chdir("/tmp"); 4.重设文件权限掩码 -- 非必要 umask(0); 5.删除进程中所有的文件描述符 getdtablesize()//得到当前 int i = 0; for(i = 0; i < ge; i++) { close(i); } while(1) { //周期性需要执行的事件 }
守护进程创建例子
创建一个守护进程,在time.log日志文件中,每隔1s ,记录当前时间
#include#include #include #include #include #include int main() { fork();//创建子进程 int pid = fork(); if (pid > 0) { exit(0); } setsid();//让子进程脱离原本会话 int i = 0; for(i=0; i < getdtablesize();i++)//删除进程中所有的文件描述符 { close(i); } while(1)//每隔一秒向日志文件写入当前时间 { int line = 0; time_t t; FILE *fp = fopen("time.log", "a+"); if(NULL == fp) { perror("fopen"); return -1; } char buf[64] = {0}; while( fgets(buf, 64, fp) != NULL) { if(buf[strlen(buf)-1] == 'n') { line++; } memset(buf, 0, 64); } struct tm * m_t= NULL; while(1) { time(&t); m_t = localtime(&t); fprintf(fp, "%d %d年%d月%d日 %d:%d:%dn", ++line, m_t->tm_year+1900,m_t->tm_mon+1,m_t->tm_mday,m_t->tm_hour, m_t->tm_min, m_t->tm_sec); fflush(fp); sleep(1); } } }
注:
1)没有日志文件先在当前目录下创建一个日志文件
2)程序运行后,该守护进程为后台运行,且只能用 kill 结束该进程;