Linux 常用的 C 语言进程 API

First Post:

Last Update:

daemon

1
2
#include<unistd.h>
int daemon(int nochdir, int noclose);

快速创建希望脱离控制台,以守护进程形式在后台运行的程序。

将进程的工作目录改为根目录,STDIN, STDOUT, STDERR 都重定向到 /dev/null。随后 fork 一个子进程。
假如运行成功,父进程在 daemon 函数运行完毕后自杀,以后的代码全部是子进程来运行。

守护进程

守护进程(daemon)是运行在后台,且一直运行的一种特殊进程。它独立于控制终端并周期性执行某种任务或者等待处理某些事件。它是一种很有用的进程,Linux 的大多数服务器就是用守护进程实现的,守护进程也完成许多系统任务。

nochdir

当为真(非 0 值)时,不把进程的工作目录改为根目录。

noclose

当为真(非 0 值)时,不重定向标准输入输出。

返回值

成功则在子进程中返回 0,在父进程中不返回。失败则返回 -1,失败原因存于 errno 中。

exec 函数族

系统调用 exec 有多种使用形式,称为 exec 函数族。它们只是在参数上不同,功能是相同的。

exec 函数族中有 6 个函数,分别为 execlexecvexecleexecveexeclpexecvp。函数中最后一个字母 l、v 分别表示参数用列表传递、参数用字符指针数组传递,e、p 分别表示可指定环境变量、可自动搜索文件。
exec 示意图
exec 函数族与 fork 函数不同。fork 产生一个新的进程,而 exec 函数族则是直接替换掉当前进程。如果你希望程序创建一个新的进程用于运行指定程序,你需要先 fork 一个新的子进程,再在子进程中调用 exec。

execl

1
2
#include<unistd.h>
int execl(const char* path, const char* arg, ...);

将当前进程的程序替换为指定的程序,然后执行该程序。

path

要执行的文件路径。

arg

执行时传入的参数,可以有多个。最后一个参数必须用空指针 NULL 作结束。

示例:

1
execl("/bin/ls", "ls", "-al", "/etc/passwd", NULL);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

execle

1
2
#include<unistd.h>
int execle(const char* path, const char* arg, ..., const char* envp[]);

将当前进程的程序替换为指定的程序,然后执行该程序。

path

要执行的文件路径。

arg

执行时传入的参数,可以有多个。最后一个参数必须用空指针 NULL 作结束。

envp

执行时传入的环境变量,是一个数组。最后一个环境变量必须用空指针 NULL 作结束。

示例:

1
2
const char* envp[] = { "PATH=/bin:/usr/bin", "TERM=xterm", NULL };
execle("/bin/ls", "ls", "-al", "/etc/passwd", NULL, envp);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾;PATH=/bin:/usr/binTERM=xterm 两个字符串作为环境变量,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

execlp

1
2
#include<unistd.h>
int execlp(const char* file, const char* arg, ...);

将当前进程的程序替换为指定的程序,然后执行该程序。

file

要执行的文件名,系统会自动在环境变量 PATH 指定的各目录中搜索该文件。

arg

执行时传入的参数,可以有多个。最后一个参数必须用空指针 NULL 作结束。

示例:

1
execlp("ls", "ls", "-al", "/etc/passwd", NULL);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

execv

1
2
#include<unistd.h>
int execv(const char* path, const char* argv[]);

将当前进程的程序替换为指定的程序,然后执行该程序。

path

要执行的文件路径。

argv

执行时传入的参数,是一个数组。最后一个参数必须用空指针 NULL 作结束。

示例:

1
2
const char* argv[] = { "ls", "-al", "/etc/passwd", NULL };
execv("/bin/ls", argv);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

execve

1
2
#include<unistd.h>
int execve(const char* path, const char* argv[], const char* envp[]);

将当前进程的程序替换为指定的程序,然后执行该程序。

path

要执行的文件路径。

argv

执行时传入的参数,是一个数组。最后一个参数必须用空指针 NULL 作结束。

envp

执行时传入的环境变量,是一个数组。最后一个环境变量必须用空指针 NULL 作结束。

示例:

1
2
3
const char* argv[] = { "ls", "-al", "/etc/passwd", NULL };
const char* envp[] = { "PATH=/bin:/usr/bin", "TERM=xterm", NULL };
execve("/bin/ls", argv, envp);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾;PATH=/bin:/usr/binTERM=xterm 两个字符串作为环境变量,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

execvp

1
2
#include<unistd.h>
int execvp(const char* file, const char* argv[]);

将当前进程的程序替换为指定的程序,然后执行该程序。

file

要执行的文件名,系统会自动在环境变量 PATH 指定的各目录中搜索该文件。

argv

执行时传入的参数,是一个数组。最后一个参数必须用空指针 NULL 作结束。

示例:

1
2
const char* argv[] = { "ls", "-al", "/etc/passwd", NULL };
execv("ls", argv);

此处传入了 ls-al/etc/passwd 三个字符串作为参数,并以 NULL 结尾。

返回值

如果执行成功则函数不会返回,执行失败则直接返回 -1,失败原因存于 errno 中。

fork

1
2
#include<unistd.h>
pid_t fork();

建立一个新的进程并作为子进程运行。将会拷贝父进程的程序、变量等几乎全部内存数据,并在父进程和子进程的程序上下文中返回不同值。
fork 示意图

返回值

成功则在子进程中返回 0,在父进程中返回子进程的进程号。失败则返回 -1,失败原因存于 errno 中。

注意:子进程的信息是复制而来,而不是与父进程共享。在子进程中修改变量不会影响父进程。

getpid

1
2
#include<unistd.h>
pid_t getpid();

取得当前进程的进程号。

返回值

当前进程的进程标识符。

getppid

1
2
#include<unistd.h>
pid_t getppid();

取得当前进程的父进程号。

返回值

当前进程的父进程标识符。

kill

1
2
#include<signal.h>
int kill(pid_t pid, int sig);

向指定的进程发送给定的信号。

pid

指定要接收信号的进程。

  • pid > 0 代表将信号传给进程识别码为 pid 的进程;
  • pid = 0 代表将信号传给和目前进程相同进程组的所有进程;
  • pid = -1 代表将信号广播传送给系统内所有的进程;
  • pid < 0 代表将信号传给进程组识别码为 pid 绝对值的所有进程。

sig

要发送的信号值。

常用信号如下表:

信号值 信号名称 说明
1 SIGHUP 表示用户终端连接(正常或非正常)结束。该信号让进程立即关闭。如果进程是守护进程,一般将重新读取配置文件之后重启。
2 SIGINT 程序中止信号,用于中止前台进程。相当于按下 Ctrl+C 快捷键。
8 SIGFPE 在发生致命的算术运算错误时发出。包括浮点运算错误,溢出及除数为 0 等所有的算术运算错误。
9 SIGKILL 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略,一般用于强制中止进程。
14 SIGALRM 时钟定时信号,alarm 函数使用该信号。
15 SIGTERM 正常结束进程的信号,kill 命令的默认信号。如果进程已经发生了问题,那么这个信号是无法正常中止进程的,需要使用 SIGKILL 信号。
18 SIGCONT 该信号可以让暂停的进程恢复执行。本信号不能被阻断。
19 SIGSTOP 该信号可以暂停前台进程,相当于按下 Ctrl+Z 快捷键。本信号不能被阻断。

这些信号均被定义为宏,直接使用信号名称即可。

返回值

执行成功则返回 0,如果有错误则返回 -1,失败原因存于 errno 中。

signal

1
2
3
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

为给定的信号设置处理函数。

signum

要处理的信号值

handler

一个函数指针,指定收到信号时的回调函数。
该参数也可以取以下值:

  • SIG_IGN: 表示忽略指定的信号;
  • SIG_DFL: 表示恢复对信号的默认处理。

示例:

1
2
void signal_handler_fun(int signum);
signal(SIGINT, signal_hander_fun);

此后程序收到 SIGINT 信号后将转到 signal_handler_fun 函数。

返回值

返回先前的信号处理函数指针,如果有错误则返回 SIG_ERR(-1)。

sleep

1
2
#include<unistd.h>
unsigned int sleep(unsigned int seconds);

让进程暂停执行一段时间,直到到达指定的时长或是被信号中断。

seconds

要暂停的时长,单位是秒。

返回值

执行成功返回 0,失败则返回剩余秒数。

wait

1
2
#include<sys/wait.h>
pid_t wait(int* status);

暂停目前进程执行,等待一个子进程中断或结束。

status

存储子进程状态的地址。

可以使用以下宏取得一些信息:
WIFEXITED(status): 如果子进程正常结束则为真(非零值)。
WEXITSTATUS(status): 取得子进程返回的结束代码。建议先判断 WIFEXITED(status)
WIFSIGNALED(status): 如果子进程因为信号结束则为真(非零值)。
WTERMSIG(status): 取得导致子进程结束的信号代码。建议先判断 WIFSIGNALED(status)
WIFSTOPPED(status): 如果子进程处于暂停执行状态则为真(非零值)。一般只有使用了 WUNTRACED 时才会有此状况。
WSTOPSIG(status): 取得引发子进程暂停的信号代码。

返回值

成功则返回子进程的进程号,失败则返回 -1,失败原因存于 errno 中。

waitpid

1
2
#include<sys/wait.h>
pid_t wait(pid_t pid, int* status, int options);

暂停目前进程执行,等待指定子进程中断或结束。

pid

子进程的进程号。

status

存储子进程状态的地址。

options

等待选项。可以是 0 或以下宏的按位或:
WNOHANG: 如果子进程未终止则立即返回,不予等待。
WUNTRACED: 如果子进程进入暂停状态则立即返回,但对终止状态不予理会。

返回值

成功则返回子进程的进程号,失败则返回 -1,失败原因存于 errno 中。