十、Linux进程控制
进程简单的说就是一个程序一次执行的过程,它是一个动态的概念。按照教科书上的定义,进程是程序执行的实例,是linux的基本调度单位。
对于程序员来说,最重要的就是要区分进程和程序的区别,程序是指一段完成功能的代码,或者说是一个工具,它是一个静态的概念,而进程,它是动态的,比如,linux的vi编辑器,它就是一段在linux下用于文本编辑的工具,那么它是一个程序,而我们在linux终端中,可以分别开启两个vi编辑器的进程。一旦提到进程,我们的脑子里就应该产生——程序从代码的第一句动态的执行到最后一句这样的一个思路。
一个进程由如下元素组成:
1.> 进程的当前上下文,即进程的当前执行状态;
2.> 进程的当前执行目录
3.> 进程访问的文件和目录
4.> 进程的访问权限,比如它的文件模式和所有权
5.> 内存和其他分配给进程的系统资源
在linux系统中,内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,运行多久以及采用什么特性运行它。内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。
OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。进程除了自身的ID外,还有父进程ID(ppid),所有进程的祖先进程是同一个进程,它叫做init进程,ID为1,init进程是内核自检后的一个启动的进程。init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。
简单来说,Linux的进程就相当于Windows系统中的任务,每个在linux下运行的程序都是一个进程,每个进程包含(属于自己唯一的)进程标识符及数据,这些数据包含进程变量,外部变量及进程堆栈等。
1.fork()
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(void)
{
pid_t who_pid;
who_pid = fork(); //建立一个子进程
if(who_pid < 0)
{
printf("fork error\n");
return 1;
}
if(who_pid == 0) //为0表示是在子进程中
{
printf("child process id is %d\n",getpid());
}
else
{
printf("parent process id is %d\n",getpid());
}
return who_pid;
}
2.exit()和_exit()
1>exit和_exit函数都是用来终止进程的。 当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。
2>exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。 exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。
3>exit()和_exit()的区别:
a._exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
b. 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr ...). exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("---liklon----\n");
printf("---eepw-------");
exit(0); //exit()
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("---liklon----\n");
printf("---eepw-------");
_exit(0); //_exit()
}
3.wait()和waitpid()
wait(等待子进程中断或结束)
定义函数 pid_t wait (int * status);
函数说明:
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数 status 可以设成 NULL。
如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回返回值-1。失败原因存于 errno 中。
waitpid(等待子进程中断或结束)
定义函数 pid_t waitpid(pid_t pid,int * status,int options);
函数说明:
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数 status 可以设成 NULL。
参数 pid 为欲等待的子进程识别码,其他数值意义如下:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
pid=-1 等待任何子进程,相当于 wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为 pid 的子进程。
参数 option :
WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待。
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回返回值-1。失败原因存于 errno 中。
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pc,pr;
if((pc = fork()) < 0)
{
printf("fork error\n");
exit(1);
}
if(pc == 0) //子进程
{
sleep(5);
exit(0);
}
else
{
do
{
pr = waitpid(pc,NULL,WNOHANG);
if(pr == 0)
{
printf("child process has not exit\n");
sleep(1);
}
}while(pr == 0);
if(pr == pc)
{
printf("child process exit %d\n",pr);
exit(0);
}
else
{
printf("error\n");
exit(1);
}
}
}
4、守护进程
守护进程
Linux大多数服务都是通过守护进程实现的,完成许多系统任务
0: 调度进程,称为交换进程(swapper),内核一部分,系统进程
1: init进程, 内核调用,负责内核启动后启动Linux系统没有终端限制
让某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个守护进程
进程组:一个或多个进程的集合
会话: 一个或多个进程组的集合开始于用户登录终止于用户退出此期间所有进程都属于这个会话期
守护进程编程步骤
1. 创建子进程,父进程退出
•所有工作在子进程中进行
•形式上脱离了控制终端
2. 在子进程中创建新会话
•setsid()函数
•使子进程完全独立出来,脱离控制
3. 改变当前目录为根目录
•chdir()函数
•防止占用可卸载的文件系统
•也可以换成其它路径
4. 重设文件权限掩码
•umask()函数
•防止继承的文件创建屏蔽字拒绝某些权限
•增加守护进程灵活性
5. 关闭文件描述符
•继承的打开文件不会用到,浪费系统资源,无法卸载
•getdtablesize()
•返回所在进程的文件描述符表的项数,即该进程打开的文件数目
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t res;
int i,fd_test;
char *buf = "hi eepw--liklon daemon test\n";
if((res = fork()) < 0)
{
printf("fork error\n");
exit(1);
}
else if(res > 0)
{
exit(0); //主进程退出
}
setsid(); //建立新的会话
chdir("/");//改变工作路径
umask(0); //重设文件掩码
for(i = 0;i < getdtablesize();i++)
{
close(i); //关闭文件描述符
}
while(1)
{
if((fd_test = open("/tmp/daemon.log",O_RDWR | O_CREAT | O_APPEND,777)) < 0)
{
printf("open file error\n");
exit(1);
}
write(fd_test,buf,strlen(buf) + 1);
close(fd_test);
sleep(10);
}
exit(0);
}