进程
1 进程标识符pid
基本概念
类型pid_t,是一个有符号16位整数,进程号是顺次向下使用(fd是优先使用当前可用最小的)
shell中的ps命令能够查看当前系统的进程信息快照
相关函数
- getpid(2)获取当前进程的进程号
/* Get the process ID of the calling process. */
extern __pid_t getpid (void) __THROW;
- getppid(2)获取当前进程父进程的进程号
/* Get the process ID of the calling process's parent. */
extern __pid_t getppid (void) __THROW;
2 进程的产生——fork(2)
fork(2)
- fork() 创建一个子进程,并返回其pid
/* Clone the calling process, creating an exact copy.
Return -1 for errors, 0 to the new process,
and the process ID of the new process to the old process. */
extern __pid_t fork (void) __THROWNL;
-
fork()是通过复制的方式创建一个和父进程几乎一样的进程,连执行到的代码都一样,这两个进程不一样的的地方如下:
- fork()的返回值不一样
- 父子进程的pid,ppid不同
- 未决信号和文件锁不继承
- 资源利用量清0
-
返回值:
- 失败返回给父进程-1
- 成功返回给父进程子进程的pid,子进程中返回0
-
注意fork()对于内存空间采用了写时复制的技术,就是说在创建子进程时,并没有将父进程的物理内存完全复制一份,而是将其页表复制了一份,并将这块内存的权限设置为只读,当父进程或子进程要对某块内存执行写操作的时候就会触发CPU的写保护中断,操作系统就会执行写保护中断函数,在这个函数中会将执行写操作的数据页复制一份,并重新设置其内存映射关系后将父子进程的这些数据页设置为可读写的
-
示例1:
int main(int argc, char **argv) {
pid_t pid;
printf("[%d]:Begin!\n", getpid());
fflush(NULL); //在fork()前要刷新所有的流
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
sleep(1);
if (pid == 0) {
printf("[%d]:Child is working...\n", getpid());
} else {
printf("[%d]:Parent is working...\n", getpid());
}
printf("[%d]:End!\n", getpid());
exit(0);
}
输出结果:可以看到两个进程出现了交替执行的情况,在实际生产环境中,永远不要凭空猜测父子进程哪一个先运行,这是由调度器的调度策略决定的
[8184]:Begin!
[8184]:Parent is working...
[8185]:Child is working...
[8184]:End!
[8185]:End!
注意:在调用fork()前一定要执行fflush()刷新所有的流,若没有fflush()则输出结果如下:可以看到begin执行的两次,且进程号都是父进程的,因为当我们将./main执行结果重定向到out文件中的时候就不再是stdout了,也就不再是行缓冲,而是全缓冲,由于子进程将父进程的内存复制一份,所以缓冲区中的"[10397]:Begin!"也复制了一份
root@VM-24-2-ubuntu:/home/ubuntu/linux# ./main > ./out
root@VM-24-2-ubuntu:/home/ubuntu/linux# cat out
[10397]:Begin!
[10397]:Parent is working...
[10397]:End!
[10397]:Begin!
[10398]:Child is working...
[10398]:End!
-
示例2:输出30000000到30000200之间的质数
- 单进程环境下:
#define LEFT 30000000 #define RIGHT 30000200 int main(int argc, char **argv) { int i, j, mark; for (i = LEFT; i <= RIGHT; i++) { mark = 1; for (j = 2; j < i / 2; j++) { if (i % j == 0) { mark = 0; break; } } if (mark) { printf("%d is a primer\n", i); } } exit(0); }
通过time命令查看输出的时间:
root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main > /dev/null real 0m0.829s user 0m0.829s sys 0m0.000s
- 多进程情况,每得到一个i就交给一个进程来计算
#define LEFT 30000000 #define RIGHT 30000200 int main(int argc, char **argv) { int i, j, mark; pid_t pid; for (i = LEFT; i <= RIGHT; i++) { mark = 1; pid = fork(); if (pid < 0) { perror("fork():"); exit(1); } if (pid == 0) { for (j = 2; j < i / 2; j++) { if (i % j == 0) { mark = 0; break; } } if (mark) { printf("%d is a primer\n", i); } break; //一定得break,不然每个子进程又会进行外层循环从而到再次创建进程,最终可能导致系统崩溃 } } exit(0); }
通过time命令查看输出的时间:可以看到real time减少了非常多,同时内核态执行的时间大幅增加
root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main > out real 0m0.058s user 0m0.000s sys 0m0.009s
init进程
init进程:1号进程,是所有进程的祖先进程
3 进程的消亡及释放资源
相关函数
- wait() 将收尸的子进程的状态放到wstatus中去,该函数是阻塞
/* Wait for a child to die. When one does, put its status in *wstatus
and return its process ID. For errors, return (pid_t) -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern __pid_t wait (int *wstatus);
- waitpid() 收尸指定pid的进程,options提供了一些选项来指定wait的状态(阻塞?非阻塞等)
/* Wait for a child matching PID to die.
If PID is greater than 0, match any process whose process ID is PID.
If PID is (pid_t) -1, match any process. //收取任何一个子进程
If PID is (pid_t) 0, match any process with the //收取同一组中该进程的子进程
same process group as the current process.
If PID is less than -1, match any process whose //收取PID绝对值对应进程组中的子进程
process group is the absolute value of PID.
If the WNOHANG bit is set in OPTIONS, and that child
is not already dead, return (pid_t) 0. If successful,
return PID and store the dead child's status in wstatus.
Return (pid_t) -1 for errors. If the WUNTRACED bit is
set in OPTIONS, return status for stopped children; otherwise don't.
This function is a cancellation point and therefore not marked with
__THROW. */
extern __pid_t waitpid (__pid_t __pid, int *wstatus, int __options);
options
The value of options is an OR of zero or more of the following constants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced via ptrace(2)). Status for traced children which have stopped is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery of SIGCONT.
wait函数就等于waitpid(-1, &wstatus, 0)
- waitid()
/* Wait for a childing matching IDTYPE and ID to change the status and
place appropriate information in *INFOP.
If IDTYPE is P_PID, match any process whose process ID is ID.
If IDTYPE is P_PGID, match any process whose process group is ID.
If IDTYPE is P_ALL, match any process.
If the WNOHANG bit is set in OPTIONS, and that child
is not already dead, clear *INFOP and return 0. If successful, store
exit code and status in *INFOP.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int waitid (idtype_t __idtype, __id_t __id, siginfo_t *__infop,
int __options);
wstatus的判断
拿到wstatus后可以用以下宏来判断子进程的状态
WIFEXITED(wstatus)
returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
WEXITSTATUS(wstatus)
returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argu‐
ment for a return statement in main(). This macro should be employed only if WIFEXITED returned true.
WIFSIGNALED(wstatus)
returns true if the child process was terminated by a signal.
WTERMSIG(wstatus)
returns the number of the signal that caused the child process to terminate. This macro should be employed only if WIFSIGNALED returned true.
WCOREDUMP(wstatus)
returns true if the child produced a core dump. This macro should be employed only if WIFSIGNALED returned true.
This macro is not specified in POSIX.1-2001 and is not available on some UNIX implementations (e.g., AIX, SunOS). Therefore, enclose its use inside #ifdef WCOREDUMP ... #endif.
WIFSTOPPED(wstatus)
returns true if the child process was stopped by delivery of a signal; this is possible only if the call was done using WUNTRACED or when the child is being traced (see ptrace(2)).
WSTOPSIG(wstatus)
returns the number of the signal which caused the child to stop. This macro should be employed only if WIFSTOPPED returned true.
WIFCONTINUED(wstatus)
(since Linux 2.6.10) returns true if the child process was resumed by delivery of SIGCONT.
示例1——wait()函数
int main(int argc, char **argv) {
int i;
pid_t pid;
for (i = 0; i <= 2; i++) {
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
if (pid == 0) {
printf("我是子进程【%d】\n", getpid());
exit(0);
}
}
for (i = 0; i <= 2; i++) {
//pid = wait(&st);
pid = wait(NULL);
printf("【%d】子进程退出了\n", pid);
}
printf("主进程退出...\n");
exit(0);
}
执行结果:可以看到主进程在所有子进程退出后才退出
我是子进程【1046】
【1046】子进程退出了
我是子进程【1047】
我是子进程【1048】
【1047】子进程退出了
【1048】子进程退出了
主进程退出..
示例2——waitpid()
int main(int argc, char **argv) {
int i;
pid_t pid;
for (i = 0; i <= 2; i++) {
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
if (pid == 0) {
printf("我是子进程【%d】\n", getpid());
exit(0);
}
}
for (i = 0; i <= 2; i++) {
while((pid = waitpid(-1, NULL, WNOHANG)) == 0) {
printf("我是父进程,接着做我的事了....");
};
printf("【%d】子进程退出了\n", pid);
}
printf("主进程退出...\n");
exit(0);
}
这个程序会打印非常多的"我是父进程,接着做我的事了…",说明了加上WNOHANG参数后waitpid是非阻塞的
wait(),waitpid()函数的作用
- 进程创建子进程的目的可能是需要子进程分担一些任务,使用wait函数能够使得父进知道子进程的工作状态从而做出后序操作
- 保证父进程在所有子进程退出后才退出
- 当子进程执行结束但是内存中代表进程的数据结构还没被回收时,该子进程就是一个僵尸进程,若父进程也结束了那么该僵尸进程会被init进程领养,使用wait函数就能够回收僵尸子进程,从而避免内存泄漏,主要是能够回收其pid(pid是有限的)
4 进程分配
之前3.2中计算质数的任务时对于每个质数都创建了一个进程来进行计算,进程的创建和销毁以及上下文切换是需要时间的,创建这么多进程,每个进程又只完成计算一个数的任务,这种分配可能是得不偿失,出现进程状态转变所需时间比实际计算任务时间大很多的情况,所以我们可以考虑其他的任务分配方式,比如将这个201个数的计算分配给N个进程,每个进程指向若干个数的计算。分配方法有如下几种:
-
分块分配,将201个数顺序分为N块,每个进行计算某一块的数据
-
交叉分配:
比如现在N=3,那么1号进程分配201,然后202分配给2号进程,203分配给3号进程,204又分配给1号进程,以此类推
-
使用池:上游一个进程创建一个任务池,将这201个任务挨个放到任务池中,下游有N个进程在任务池中抢任务来执行
示例——较差分配法
#define LEFT 30000000
#define RIGHT 30000200
#define N 3
int main(int argc, char **argv) {
int i, j, k, mark;
pid_t pid;
for (i = 0; i < N; i++) {
pid = fork();
if (pid < 0) {
perror("fork():");
//某个进程创建失败,需要先将已创建的进程回收后再退出
for (k = 0; k < i; k++) {
wait(NULL);
}
exit(1);
}
if (pid == 0) {
for (j = LEFT + i; j <= RIGHT; j += N) {
mark = 1;
for (k = 2; k < j / 2; k++) {
if (j % k == 0) {
mark = 0;
break;
}
}
if (mark) printf("【%d】%d is a primer\n", getpid(), j);
}
exit(0);
}
}
for (i = 0; i < N; i++) {
wait(NULL);
}
exit(0);
}
结果:可以看到数是随机的,同时第一个子进程没有打印任何数,可见这种分配方式对于该问题还是不是很理想
【7557】30000001 is a primer
【7558】30000023 is a primer
【7557】30000037 is a primer
【7558】30000041 is a primer
【7558】30000059 is a primer
【7557】30000049 is a primer
【7558】30000071 is a primer
【7557】30000079 is a primer
【7558】30000083 is a primer
【7557】30000109 is a primer
【7558】30000137 is a primer
【7557】30000133 is a primer
【7558】30000149 is a primer
【7557】30000163 is a primer
【7558】30000167 is a primer
【7557】30000169 is a primer
【7557】30000193 is a primer
【7557】30000199 is a primer
5 exec(3)函数族
问题引入
之前写的程序,main进程会创建若干个相同的main进程,需要注意的是main进程是有shell(bash)进程创建的,但是可以发现这两者是完全不一样的
exec函数族
- 函数定义
#include <unistd.h>
extern char **environ;
//用一个给出路径的的可执行文件来替换当前进程,arg是指需要传入的参数,注意这里的arg是从arg[0]开始
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
//用一个给出文件名的可执行文件(有环境变量来找到这个可执行文件的具体位置)来替换当前进程,arg是指需要传入的参数
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[]);
- 描述 用一个新的进程替换当前进程
The exec() family of functions replaces the current process image with a new process image. The functions described in this manual page are front-ends for execve(2). (See the manual page
for execve(2) for further details about the replacement of the current process image.)
-
返回值:
有返回值则表示出错没有返回值则表示已经被一个新的进程映像替换掉了
-
注意点:
和fork()一样,在调用前一定要调用fflush()刷新所有的流
示例:实现data+%s的功能
/**
* 实现data+%s打印时戳的功能
*/
int main(int argc, char **argv) {
puts("Begin!");
fflush(NULL); //!!!
execl("/bin/date", "date", "+%s", NULL);
perror("excel()");
exit(1);
puts("End!");
exit(0);
}
需要注意的是这个程序仍然存在一个问题,就是原进程做个很多事后突然变成了其他进程,那些事等于白做了,而且新进程和原进程的pid还是同一个,所以加上fork/wait函数后做以下改进:
/**
* 实现data+%s打印时戳的功能
*/
int main(int argc, char **argv) {
pid_t pid;
puts("Begin!");
fflush(NULL); //!!!
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
if (pid == 0) {
execl("/bin/date", "date", "+%s", NULL);
perror("excel():");
exit(1);
}
wait(NULL);
puts("End!");
exit(0);
}
这时就相当于创建一个子进程,该子进程的任务就是执行/exec/date
6 shell命令实现——few
shell终端就是通过fork()-execXX()-wait()命令来实现的,一个简单的shell(bash)实现如下:
具体过程就是bash进程获取一行命令行的内容,然后对其进行解析,解析后就获取到了要执行的命令名和执行该命令用到的参数,然后就fork()子进程后调用execXX()命令后由该子进程来全权负责执行解析出来的命令,对于父进程(bash)就在此处wait子进程,子进程执行完后就经过循环进入下一个命令行
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <glob.h>
#define DELIMTS " \t\n"
typedef struct {
glob_t globres;
} cmd_st;
//命令提示符
static void prompt(void) {
printf("myShell-0.1$: ");
}
//解析命令
static void parse(char *line, cmd_st *res) {
char *tok;
int i = 0;
while (1) {
tok = strsep(&line, DELIMTS);
if (tok == NULL) break;
//解析到空串""
if (tok[0] == '\0') continue;
/**
* 这里碰到一个问题,我们需要一个char*类型的数组来帮助我们存储解析出来的参数,而这个参数的个数是不 * 定的,所以需要一个结构来帮助我们存储这些参数,这时候就应该联想到main函数参数的存储,又可以联系到 * 有一个函数能够给我们实现这样的数据存储,就是glob(),里面的glob_t就是一个类main函数参数的结构 * 体,这个函数在flags中有GLOB_NOCHECK时表示当我们不能找到与pattern相匹配的参数时就直接存储这个 * pattern
*/
glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &res->globres);
i++;
}
}
/**
* 实现shell执行外部命令的功能
*/
int main(int argc, char **argv) {
pid_t pid;
char *linebuf;
size_t linebuf_size = 0;
cmd_st cmd;
while (1) {
//1 命令提示符
prompt();
//2 获取一行的输入
if (getline(&linebuf, &linebuf_size, stdin) < 0) {
break;
}
parse(linebuf, &cmd);
//3 对命令进行解析
if (0) { //3.1 内部命令
} else { //3.2 外部命令
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
} else if (pid == 0) { //子进程
execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);
perror("execvp():");
exit(1);
} else { //父进程
wait(NULL);
}
}
}
exit(0);
}
7 用户权限和组权限
相关概念
linux是一个多用户多任务系统,也就是说在某一个时刻可以有多个用户登录linux系统后来执行相关的任务,为了便于管理这些用户,linux引入了用户和组的概念,每个用户有自己的相关权限,同一个用户组中的用户又具有相同的权限,用户和用户组的关系如下图所示:
用户和组ID又分为三种,分别是real/effective/saved set,其中effective id决定了进程是否有访问某个文件的权限,三种id用法如下:
相关函数
- getuid(2) 返回当前进程的实际用户id
/* Get the real user ID of the calling process. */
extern __uid_t getuid (void) __THROW;
- geteuid(2) 返回当前进程的有效用户id
/* Get the effective user ID of the calling process. */
extern __uid_t geteuid (void) __THROW;
- getgid(2) 返回当前进程的实际组id
/* Get the real group ID of the calling process. */
extern __gid_t getgid (void) __THROW;
- getegid(2) 返回当前进程的有效组id
/* Get the effective group ID of the calling process. */
extern __gid_t getegid (void) __THROW;
- setuid(2) 设置当前进程的有效用户id
/* Set the user ID of the calling process to UID.
If the calling process is the super-user, set the real
and effective user IDs, and the saved set-user-ID to UID;
if not, the effective user ID is set to UID. */
extern int setuid (__uid_t __uid) __THROW __wur;
- setgid(2) 设置当前进程的有效组id
/* Set the group ID of the calling process to GID.
If the calling process is the super-user, set the real
and effective group IDs, and the saved set-group-ID to GID;
if not, the effective group ID is set to GID. */
extern int setgid (__gid_t __gid) __THROW __wur;
- setreuid(2) 交换real或effective用户id(该函数是原子操作)
/* Set the real user ID of the calling process to RUID,
and the effective user ID of the calling process to EUID. */
extern int setreuid (__uid_t __ruid, __uid_t __euid) __THROW __wur;
- setregid(2) 交换real或effective组id(该函数是原子操作)
/* Set the real group ID of the calling process to RGID,
and the effective group ID of the calling process to EGID. */
extern int setregid (__gid_t __rgid, __gid_t __egid) __THROW __wur;
- seteuid(2) 更改有效用户id
/* Set the effective user ID of the calling process to UID. */
extern int seteuid (__uid_t __uid) __THROW __wur;
- setegid(2) 更改有效组id
/* Set the effective group ID of the calling process to GID. */
extern int setegid (__gid_t __gid) __THROW __wur;
实际用途举例——passwd
passwd命令使用来修改用户的密码的,比如我们的普通用户就可以调用这个命令来修改自己的密码,密码是保存在/etc/shadow文件中的,这个文件只有root用户拥有修改的权限,而普通用户想要修改这个文件就必须修改当前执行者的euid,这样才能对阴影口令文件进行修改,这时suid就其作用了,当我们查看passwd的属性时可见:
root@VM-24-2-ubuntu:/home/ubuntu/linux/tmp# ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59640 Mar 14 2022 /usr/bin/passwd
可见出现了s权限标识,有了这个标识,就表示这个可执行文件在执行时(也就是execXX()该函数发现了u+s这样权限时),euid将被修改为其所有者的uid(这里passwd的uid就是0),这样我们普通用户在执行passwd权限时就能够修改阴影口令文件了。
示例
int main(int argc, char **argv) {
pid_t pid;
struct passwd *pw;
if (argc < 3) {
fprintf(stderr, "Usage...\n");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
if (pid == 0) {
pw = getpwnam(argv[1]);
setuid(pw->pw_uid);
execvp(argv[2], argv + 2);
perror("execvp():");
exit(1);
} else {
wait(NULL);
}
exit(0);
}
8 system()/进程会计/进程时间
system()
- 功能:执行一个shell命令
/* Execute the given line as a shell command.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int system (const char *__command) __wur;
- 示例
int main(int argc, char **argv) {
system("date +%s");
exit(0);
}
- 工作原理:实际上就是简单fork-exec-wait的实现,比如上面的例子system可以认为就是如下实现:
int main(int argc, char **argv) {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork():");
exit(1);
}
if (pid == 0) {
execl("/bin/sh", "sh", "-c", "date +%s", NULL);
perror("execl():");
exit(1);
} else {
wait(NULL);
}
exit(0);
}
进程会计
- acct()函数,当有进程消亡的时候会将其属性写到name文件中
/* Turn accounting on if NAME is an existing file. The system will then write
a record for each process as it terminates, to this file. If NAME is NULL,
turn accounting off. This call is restricted to the super-user. */
extern int acct (const char *__name) __THROW;
进程时间
- times(2)函数
/* Store the CPU time used by this process and all its
dead children (and their dead children) in BUFFER.
Return the elapsed real time, or (clock_t) -1 for errors.
All times are in CLK_TCKths of a second. */
extern clock_t times (struct tms *__buffer) __THROW;
- tms结构体
/* Structure describing CPU time used by a process and its children. */
struct tms
{
clock_t tms_utime; /* User CPU time. */
clock_t tms_stime; /* System CPU time. */
clock_t tms_cutime; /* User CPU time of dead children. */
clock_t tms_cstime; /* System CPU time of dead children. */
};
9 守护进程
什么是守护进程
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。
一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。
守护进程的名称通常以d结尾,比如sshd、xinetd、crond等
基本概念
-
进程组
- 每个进程也属于一个进程组
- 每个进程主都有一个进程组号,该号等于该进程组组长的PID号 。
- 一个进程只能为它自己或子进程设置进程组ID号
-
会话期
- 会话期(session)是一个或多个进程组的集合。
- setsid()函数可以建立一个对话期:如果,调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话期。
相关函数
- setsid() 创建一个session并设置进程组id
/* Create a new session with the calling process as its leader.
The process group IDs of the session and the calling process
are set to the process ID of the calling process, which is returned. */
extern __pid_t setsid (void) __THROW;
- getpgrp() 返回当前进程所在的进程组的id
/* Get the process group ID of the calling process. */
extern __pid_t getpgrp (void) __THROW;
- getpgid() 返回pid进程的进程组id
/* Get the process group ID of process PID. */
extern __pid_t __getpgid (__pid_t __pid) __THROW;
- setpgid() 将进程pid放到进程组pgid中
/* Set the process group ID of the process matching PID to PGID.
If PID is zero, the current process's process group ID is set.
If PGID is zero, the process ID of the process is used. */
extern int setpgid (__pid_t __pid, __pid_t __pgid) __THROW;
守护进程的创建
#define FNAME "/home/ubuntu/linux/out"
static int daemonize() {
pid_t pid;
int fd;
pid = fork();
if (pid < 0) {
return -1;
}
if (pid > 0) exit(0);
fd = open("/dev/null", O_RDWR);
if (fd < 0) {
return -1;
}
//关闭终端
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) close(fd);
//创建新的session
setsid();
//切换工作路径
chdir("/");
umask(0);
return 0;
}
int main(int argc, char **argv) {
FILE *fp;
int i;
openlog("mydaemon", LOG_PID, LOG_DAEMON);
//创建守护进程
if (daemonize()) {
syslog(LOG_ERR, "daemonize() failed!");
exit(1);
} else {
syslog(LOG_INFO, "daemonize() succeeded!");
}
//守护进程打开一个文件并持续向文件中写数据
fp = fopen(FNAME, "w");
if (fp == NULL) {
syslog(LOG_ERR, "fopen():%s", strerror(errno));
exit(1);
}
syslog(LOG_INFO, "%s was opened!", FNAME);
for (i = 0; ; i++) {
fprintf(fp, "%d\n", i);
fflush(fp);
syslog(LOG_DEBUG, "%d is printed.", i);
sleep(1);
}
fclose(fp);
closelog();
exit(0);
}
10 系统日志
系统日志只有syslogd服务能够写,当我们要写系统日志的时候可以通过一定的接口将要写的日志交给这个服务来帮我们写
相关函数
- openlog(3) iden指任意的一个字段,option是特殊要求
/* Open connection to system logger.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern void openlog (const char *__ident, int __option, int __facility);
option的选择:
The option argument to openlog() is a bit mask constructed by ORing together any of the following values:
LOG_CONS Write directly to the system console if there is an error while sending to the system logger.
LOG_NDELAY Open the connection immediately (normally, the connection is opened when the first message is logged). This may be useful, for example, if a subsequent chroot(2) would make the pathname used internally by the logging facility unreachable.
LOG_NOWAIT Don't wait for child processes that may have been created while logging the message. (The GNU C library does not create a child process, so this option has no effect on Linux.)
LOG_ODELAY The converse of LOG_NDELAY; opening of the connection is delayed until syslog() is called. (This is the default, and need not be specified.)
LOG_PERROR (Not in POSIX.1-2001 or POSIX.1-2008.) Also log the message to stderr.
LOG_PID Include the caller's PID with each message.
facility指来源
The facility argument is used to specify what type of program is logging the message. This lets the configuration file specify that messages from different facilities will be handled differently.
LOG_AUTH security/authorization messages
LOG_AUTHPRIV security/authorization messages (private)
LOG_CRON clock daemon (cron and at)
LOG_DAEMON system daemons without separate facility value
LOG_FTP ftp daemon
LOG_KERN kernel messages (these can't be generated from user processes)
LOG_LOCAL0 through LOG_LOCAL7reserved for local use
LOG_LPR line printer subsystem
LOG_MAIL mail subsystem
LOG_NEWS USENET news subsystem
LOG_SYSLOG messages generated internally by syslogd(8)
LOG_USER (default)generic user-level messages
LOG_UUCP UUCP subsystem
- syslog(3) 提交内容,priority表示级别,重要程度,fmt就是提交的内容
/* Generate a log message using FMT string and option arguments.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern void syslog (int __pri, const char *__fmt, ...)
__attribute__ ((__format__ (__printf__, 2, 3)));
级别的选项
This determines the importance of the message. The levels are, in order of decreasing importance:
LOG_EMERG system is unusable
LOG_ALERT action must be taken immediately
LOG_CRIT critical conditions
LOG_ERR error conditions
LOG_WARNING warning conditions
LOG_NOTICE normal, but significant, condition
LOG_INFO informational message
LOG_DEBUG debug-level message
- closelog(3) 断开服务
/* Close descriptor used to write to system logger.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern void closelog (void);
===