1. Linux进程概述
进程是一个程序一次执行的过程,它和程序有本质区别。
程序是静态的,它是一些保存在磁盘上的指令的有序集合;而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡的过程,是Linux的基本调度单位。
那么从系统的角度看如何描述并表示它的变化呢?在这里,是通过进程控制块(PCB)来描述的。进程控制块包含了进程的描述信息、控制信息以及资源信息,它是进程的一个静态描述。
内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,运行多久以及采用什么特性运行它。内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。
1.1. 进程标识
OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。进程除了自身的ID外,还有父进程ID(ppid),所有进程的祖先进程是同一个进程,它叫做init进程,ID为1,init进程是内核自举后的一个启动的进程。init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。
进程的pid和ppid可以分别通过函数getpid()和getppid()获得。
1.2. 进程的用户ID与组ID(进程的运行身份)
进程在运行过程中,必须具有一类似于用户的身份,以便进行进程的权限控制,缺省情况下,哪个登录用户运行程序,该程序进程就具有该用户的身份。例如,假设当前登录用户为gotter,他运行了ls程序,则ls在运行过程中就具有gotter的身份,该ls进程的用户ID和组ID分别为gotter和gotter所属的组。这类型的ID叫做进程的真实用户ID和真实组ID。真实用户ID和真实组ID可以通过函数getuid()和getgid()获得。
与真实ID对应,进程还具有有效用户ID和有效组ID的属性,内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID。缺省情况下,用户的(有效用户ID和有效组ID)与(真实用户ID和真实组ID)是相同的。有效用户id和有效组id通过函数geteuid()和getegid()获得。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 int main() 5 { 6 printf("uid:%d gid:%d euid:%d egid:%d\n",getuid(),getgid(),geteuid(),getegid()); 7 return 0; 8 }
运行结果:
uid=500(ghaha) gid=500(ghaha) groups=500(ghaha)
编译生成可执行文件a.out,程序文件的属性可能为:
-rwxrwxr-x 1 ghaha ghaha 12132 Oct 7 09:26 a.out
执行结果可能为:
shell>./a.out
uid:500 gid:500 euid:500 egid:500
现在将a.out的所有者可执行属性改为s
shell>chmod u+s a.out
shell>ls -l
-rwsrwxr-x 1 ghaha ghaha 12132 Oct 7 09:26 a.out
此时改另外一个用户gotter登录并运行程序a.out
shell>id
uid=502(gotter) gid=502(gotter) groups=502(gotter)
shell>./a.out
uid:502 gid:502 euid:500 egid:502
可以看到,进程的有效用户身份变为了ghaha,而不是gotter了,这是因为文件a.out的访问权限的所有者可执行为设置了s的属性,设置了该属性以后,用户运行a.out时,a.out进程的有效用户身份将不再是运行a.out的用户,而是a.out文件的所有者。
s权限最常见的例子是
/usr/bin/passwd程序,它的权限位为
shell>ls /usr/bin/passwd
-r-s--x--x 1 root root 16336 Feb 13 2003 /usr/bin/passwd
我们知道,用户的用户名和密码是保存在/etc/passwd(后来专门将密码保存在/etc/shadow,它是根据/etc/passwd文件来生成/etc/shadow的,它把所有口令从/etc/passwd中移到了/etc/shadow中。这里用到的是影子口令,它将口令文件分成两部分:/etc/passwd和/etc/shadow,此时/etc/shadow就是影子口令文件,它保存的是加密的口令,而/etc/passwd中的密码全部变成x)下的。通过ls –l查看/etc/passwd这个文件,你会发现,这个文件普通用户都没有可写的权限,那我们执行passwd的时候确实能够修改密码,那么这是怎么回事呢?也就是说,任何一个用户运行该程序时,该程序的有效身份都将是root(用普通身份去执行这个操作的时候,它会暂时得到文件拥有者root的权限),而这样passwd程序才有权限读取/etc/passwd文件的信息。
我们也来实现以下passwd的功能,实现步骤如下:
1. 用touch创建一个1.txt输入内容“hello” 类似于/etc/passwd
2. 写一个程序3.c如下:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<sys/types.h> 5 #include<errno.h> 6 7 int main() 8 { 9 printf("uid = %d gid = %d\n",getuid(),getgid()); 10 printf("euid = %d egid = %d\n",geteuid(),getegid()); 11 12 FILE *fp = fopen("1.txt","a"); 13 if(NULL == fp) 14 { 15 printf("fopen error!\n"); 16 exit(-1); 17 } 18 fputs("world\n",fp); 19 fclose(fp); 20 return 0; 21 }
3. 编译gcc –o 1 1.c生成可执行程序1 此时1类似于/usr/bin/passwd文件
4. 用ll查看a.txt发现权限是-rw-r—r—的权限(跟/etc/passwd权限一样),说明普通用户没有可写权限,如果直接切换到普通用户执行./1会报错。
5. 用root身份将1的权限修改为-rwsr-xr-x (操作命令:chmod u+s 1,此时跟/usr/bin/passwd的权限一样),此时再切换到普通用户wangxiao,执行./1发现可以执行成功,因为此时./1进程的有效用户id变成root了,也就是说普通用户是借助root身份来实现的。
1.3. 进程的状态
进程是程序的执行过程,根据它的生命周期可以划分成3种状态。
1执行态:该进程正在运行,即进程正在占用CPU。
2就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片。
3等待态:进程不能使用CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。
1.4. Linux下的进程结构
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,进程之间是分离的任务,拥有各自的权利和责任。其中,每个进程都运行在各自独立的虚拟地址空间,因此,即使一个进程发生了异常,它也不会影响到系统的其他进程。
Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。
- “数据段”放全局变量、常数以及动态数据分配的数据空间。数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量)以及堆(存放动态分配的数据)。
- “代码段”存放的是程序代码的数据。
- “堆栈段”存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。
1.5. Linux下的进程管理
启动进程:手工启动 调度启动
备注:
进程process:是os的最小单元 os会为每个进程分配大小为4g的虚拟内存空间,其中 1g给内核空间 3g给用户空间{代码区 数据区 堆栈区}
ps查看活动进程
ps –aux查看所有的进程
ps -aux| grep ‘aa‘查找指定(aa)进程
ps –ef可以显示父子进程关系 top显示前20条进程,动态的改变
pgrep ‘vi‘查找进程
进程状态:执行 就绪 等待状态
ps -aux看%cpu(cpu使用量) %mem(内存使用量) stat状态{S睡眠 T暂停 R运行 Z僵尸}
vi a.c &(&表示后台运行),一个死循环,按ctrl+z可以把进程暂停,再执行[bg作业ID]可以将该进程带入后台。利用jobs可以查看后台任务,fg 1把后台任务带到前台,这里的1表示作业ID
kill -9 进程号表示向某个进程发送9号信号,从而杀掉某个进程 利用pkill a可以杀死进程名为a的进程
2. 进程的创建
Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen()
2.1. system函数
原型:
#include <stdlib.h>
int system(const char *string);
system函数通过调用shell程序/bin/sh –c来执行string所指定的命令,该函数在内部是通过调用execve(“/bin/sh”,..)函数来实现的。通过system创建子进程后,原进程和子进程各自运行,相互间关联较少。如果system调用成功,将返回0。
示例:
1 #include <stdio.h> 2 #include <stdlib.h> 3 int main() 4 { 5 system("ls -l"); //system(“clear”);表示清屏 6 return 0; 7 }
此外,system函数后面的参数还可以是一个可执行程序,例如:system(“/home/cp/1”);如果想要执行system后面进程的时候,不至于对当前进程进行阻塞,可以利用&将/home/cp/1调到后台运行。
2.2. fork函数
原型:
#include <unistd.h>
pid_t fork(void);
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。它和其他函数的区别在于:它执行一次返回两个值。其中父进程的返回值是子进程的进程号,而子进程的返回值为0.若出错则返回-1.因此可以通过返回值来判断是父进程还是子进程。
fork函数创建子进程的过程为:
使用fork函数得到的子进程是父进程的一个复制品,它从父进程继承了进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端,
而子进程所独有的只有它的进程号、资源使用和计时器等。
通过这种复制方式创建出子进程后,原有进程和子进程都从函数fork返回,各自继续往下运行,但是原进程的fork返回值与子进程的fork返回值不同,在原进程中,fork返回子进程的pid,而在子进程中,fork返回0,如果fork返回负值,表示创建子进程失败。(vfork函数)
示例:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 5 int main() 6 { 7 printf("parent process ID:%d\n",getpid()); 8 pid_t iRet = fork(); 9 10 if(iRet < 0) 11 printf("Create child process fail!\n"); 12 13 else if(iRet == 0) 14 printf("child process id:%d ppid:%d\n",getpid(),getppid()); 15 16 else 17 printf("parent process success, child id:%d\n",iRet); 18 return 0; 19 }
//有人可能会有疑问:这里怎么if和else里面的语句都得到执行了,和我们以前的if…else结构相矛盾啊?此时相当于有两份main函数代码的拷贝,其中一份做的操作是if(iRet == 0)的情况;另外一份做的操作是else(父)的情况。所以可以输出2句话。提问:如何创建兄弟进程和爷孙进程?
2.3. exec函数族
exec*由一组函数组成
int execl(const char *path, const char *arg, ...)
exec函数族的工作过程与fork完全不同,fork是在复制一份原进程,而exec函数是用exec的第一个参数指定的程序覆盖现有进程空间(也就是说执行exec族函数之后,它后面的所有代码不在执行)。
path是包括执行文件名的全路径名
arg是可执行文件的命令行参数,多个用,分割注意最后一个参数必须为NULL。
例如,有个加法程序,从命令行接受两个数,输出其和 。
代码如下:
//add.c
#inclue<stdio.h>
#include <string.h>
Int main(int argc , char * argv[])
{
Int a = atoi(argv[1]) ;
Int b = atoi(argv[2]);
Print f(“%d + %d = %d” , a , b , a + b);
Return 0 ;
}
编译连接得到add.exe.
Gcc –o add.exe add.c
然后在main.exe 中调用 add.exe 程序 ,计算3 和 4 的和。
Main.c 的源程序为 ,
//main.c
#include <stdio.h>
#include <string.h>
Int main()
{
Execl(“./add.exe” ,”add.exe” ,”3” , “4” , NULL):
Return 0 ;
}
编译连接得,
Gcc –o main.exe main.c
然后运行 。./main.exe。
在运行main.exe的过程中会通过execl启动之前的add.exe 程序。
2.4popen函数
popen函数类似于system函数,与system的不同之处在于它使用管道工作。原型为:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
command为可执行文件的全路径和执行参数;
type可选参数为”r”或”w”,如果为”w”,则popen返回的文件流做为新进程的标准输入流,即stdin,如果为”r”,则popen返回的文件流做为新进程的标准输出流。
如果type是“r”,(即command命令执行的输出结果作为当前进程的输入结果)。被调用程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出;如果tpye是“w”,(即当前进程的输出结果作为command命令的输入结果)。调用程序就可以用fwrite向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。
pclose等待新进程的结束,而不是杀新进程。
示例:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE *read_fp;
char buffer[BUFSIZ + 1];
int chars_read;
memset(buffer, ‘\0‘, sizeof(buffer));
read_fp = popen("ps -ax", "r");
if (read_fp != NULL) {
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while (chars_read > 0) {
buffer[chars_read - 1] = ‘\0‘;
printf("Reading:-\n %s\n", buffer);
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
3. 进程控制与终止
3.1. 进程的控制
用fork函数启动一个子进程时,子进程就有了它自己的生命并将独立运行。
如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被PID为1的进程(即init)接管。孤儿进程退出后,它的清理工作有祖先进程init自动处理。但在init进程清理子进程之前,它一直消耗系统的资源,所以要尽量避免。
Example1:写一个孤儿进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
pid_t pid = fork();
if( pid == 0)
{
while(1) ;
}
else
{
exit(10);
}
}
通过ps –ef就可以看到此时子进程一直在运行,并且父进程是1号进程。
如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸(zombie)进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait和waitpid都将暂停父进程,等待一个已经退出的子进程,并进行清理工作;
wait函数随机地等待一个已经退出的子进程,并返回该子进程的pid;
waitpid等待指定pid的子进程;如果为-1表示等待所有子进程。
status参数是传出参数,存放子进程的退出状态;通常用下面的两个宏来获取状态信息:
WIFEXITED(stat_val) 如果子进程正常结束,它就取一个非0值。
WEXITSTATUS(stat_val) 如果WIFEXITED非零,它返回子进程的退出码
options用于改变waitpid的行为,其中最常用的是WNOHANG,它表示无论子进程是否退出都将立即返回,不会将调用者的执行挂起。
Example1:写一个僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
pid_t pid = fork();
if( pid == 0 )
{
exit(10);
}
else
{
sleep(10);
}
}
通过用ps –aux快速查看发现Z的僵尸进程。
Example2:避免僵尸进程:(wait()函数)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
pid_t pid = fork();
if( pid == 0 )
{
exit(10);
}
else
{
wait(NULL); //NULL表示等待所有进程
sleep(10); //通常要将sleep放在wait的后面,要不然也会出现僵尸进程
}
}
Example3:利用信号处理避免僵尸进程:(wait ()函数)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void SignChildPsExit(int iSignNo)
{
int iExitCode;
pid_t pid = wait(&iExitCode); //等待子进程的退出,没有这句会出现僵尸进程
printf("SignNo:%d child %d exit\n",iSignNo,pid);
if(WIFEXITED(iExitCode))
{
printf("Child exited with code %d\n", WEXITSTATUS(iExitCode));
}
sleep(10);
}
int main()
{
signal(SIGCHLD, SignChildPsExit);
printf("Parent process id:%d\n", getpid());
pid_t iRet = fork();
if(iRet == 0)
exit(3);
}
Example4:waitpid实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void SignChildPsExit(int iSignNo)
{
int iExitCode;
pid_t pid = waitpid(-1,NULL,0); //表示等待任何进程,并阻塞。如果换成waitpid(-1,NULL,WNOHANG);则跟没有写waitpid效果类似,此时父进程没有阻塞
printf("SignNo:%d child %d exit\n",iSignNo,pid);
if(WIFEXITED(iExitCode))
{
printf("Child exited with code %d\n", WEXITSTATUS(iExitCode));
}
sleep(10);
}
int main()
{
signal(SIGCHLD, SignChildPsExit);
printf("Parent process id:%d\n", getpid());
pid_t iRet = fork();
if(iRet == 0)
exit(3);
}
3.2. 进程的终止
进程的终止有5种方式:
l main函数的自然返回;
l 调用exit函数
l 调用_exit函数
l 调用abort函数
l 接收到能导致进程终止的信号ctrl+c SIGINT ctrl+\ SIGQUIT
前3种方式为正常的终止,后2种为非正常终止。但是无论哪种方式,进程终止时都将执行相同的关闭打开的文件,释放占用的内存等资源。只是后两种终止会导致程序有些代码不会正常的执行比如对象的析构、atexit函数的执行等。
exit和_exit函数都是用来终止进程的。当程序执行到exit和_exit时,进程会无条件的停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本程序的运行。但是它们是有区别的,exit和_exit的区别如图所示:
exit函数和_exit函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的“清理I/O缓冲”。
由于linux的标准函数库中,有一种被称作“缓冲I/O”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也为编程带来了麻烦。比如有一些数据,认为已经写入文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit函数直接将进程关闭,缓冲区中的数据就会丢失。因此,如想保证数据的完整性,建议使用exit函数。
exit和_exit函数的原型:
#include <stdlib.h> //exit的头文件
#include <unistd.h> //_exit的头文件
void exit(int status);
void _exit(int status);
status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其他的数值表示出现了错误,进程非正常结束。
Example1:exit的举例如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Using exit...\n");
printf("This is the content in buffer");
exit(0);
}
可以发现,调用exit函数,缓冲区中的记录也能正常输出。
Example2:_exit的举例如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Using _exit...\n");
printf("This is the content in buffer");
_exit(0);
}
可以发现,最后的输出结果没有This is the content in buffer,说明_exit函数无法输出缓冲区中的记录。
4. 进程间打开文件的继承
4.1. 用fork继承打开的文件
fork以后的子进程自动继承了父进程的打开的文件,继承以后,父进程关闭打开的文件不会对子进程造成影响。
示例:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
char szBuf[32] = {‘\0‘};
int iFile = open("./a.txt", O_RDONLY);
if(fork() > 0){//parent process
close(iFile);
return 0;
}
//child process
sleep(3); //wait for parent process closing fd
if(read(iFile, szBuf, sizeof(szBuf)-1) < 1){
perror("read fail");
}else{
printf("string:%s\n",szBuf);
}
close(iFile);
return 0;
}
4.2. 守护进程
Daemon运行在后台也称作“后台服务进程”。 它是没有控制终端与之相连的进程。它独立与控制终端、通常周期的执行某种任务。那么为什么守护进程要脱离终端后台运行呢?守护进程脱离终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的任何终端信息所打断。那么为什么要引入守护进程呢?由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个终端,这个终端就称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它被执行开始运转,直到整个系统关闭时才退出。几乎所有的服务器程序如Apache和wu-FTP,都用daemon进程的形式实现。很多Linux下常见的命令如inetd和ftpd,末尾的字母d通常就是指daemon。
守护进程的特性:
1> 守护进程最重要的特性是后台运行。
2> 其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录已经文件创建掩码等。这些环境通常是守护进程从父进程那里继承下来的。
3> 守护进程的启动方式
daemon进程的编程规则
l 创建子进程,父进程退出:
调用fork产生一个子进程,同时父进程退出。我们所有后续工作都在子进程中完成。这样做我们可以交出控制台的控制权,并为子进程作为进程组长作准备;由于父进程已经先于子进程退出,会造成子进程没有父进程,变成一个孤儿进程(orphan)。每当系统发现一个孤儿进程,就会自动由1号进程收养它,这样,原先的子进程就会变成1号进程的子进程。代码如下:
pid = fork();
if(pid>0)
exit(0);
l 在子进程中创建新会话:
使用系统函数setsid()。由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。由于在调用fork函数的时候,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端并没有改变,因此,还不是真正意义上的独立开来。而调用setsid函数会创建一个新的会话并自任该会话的组长,调用setsid函数有下面3个作用:让进程摆脱原会话的控制,让进程摆脱原进程组的控制,让进程摆脱原控制终端的控制;
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID(GID)也是一个进程的必备属性。每个进程都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因为组长进程的退出而受影响。
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。
控制终端:由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依赖这个控制终端。
l 改变当前目录为根目录:
使用fork函数创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件是不能卸载的,这对以后的使用会造成很多的不便。利用chdir("/");把当前工作目录切换到根目录。
l 重设文件权限掩码:
umask(0);将文件权限掩码设为0,Deamon创建文件不会有太大麻烦;
l 关闭所有不需要的文件描述符:
新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,而它们一直消耗系统资源。另外守护进程已经与所属的终端失去联系,那么从终端输入的字符不可能到达守护进程,守护进程中常规方法(如printf)输出的字符也不可能在终端上显示。所以通常关闭从0到MAXFILE的所有文件描述符。
for(i=0;i<MAXFILE;i++)
close(i);
(注:有时还要处理SIGCHLD信号signal(SIGCHLD, SIG_IGN);防止僵尸进程(zombie))
下面就可以添加任何你要daemon做的事情
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
void Daemon()
{
const int MAXFD=64;
int i=0;
if(fork()!=0) //父进程退出
exit(0);
setsid(); //成为新进程组组长和新会话领导,脱离控制终端
chdir("/"); //设置工作目录为根目录
umask(0); //重设文件访问权限掩码
for(;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件
close(i);
}
int main()
{
Daemon(); //成为守护进程
while(1){
sleep(1);
}
return 0;
}
Example:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/stat.h>
#include <time.h>
main()
{
int i = 0;
if(fork() > 0)
exit(0);
setsid();
chdir("/");
umask(0);
for(; i < 64; i++)
{
close(i);
}
i = 0;
while(i < 10)
{
printf("%d\n",i);
time_t ttime;
time(&ttime);
struct tm *pTm = gmtime(&ttime);
syslog(LOG_INFO,"%d %04d:%02d:%02d", i, (1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday));
i++;
sleep(2);
}
}
通过查看vi /var/log/messages