目录
概述
本文介绍linux进程相关的函数接口,详细介绍每个函数的功能和使用方法。然后使用这些接口,编写3个范例,便于理解其在实际编程的应用方法。
1 进程基本操作
1.1 fork函数
一个现有的进程可以调用 fork()函数创建一个新的进程,
父进程: 调用 fork()函数的进程称为父进程
子进程: 由 fork()函数创建出来的进程被称为子进程(child process)
fork()函数原型如下所示(fork()为系统调用) :
#include <unistd.h>
pid_t fork(void);
fork()函数从运行着的进程中分裂出一个子进程,它通过拷贝父进程的方式创建子进程。 子进程与父进程有相同的代码空间、文件描述符等资源。
进程创建后,子进程与父进程开始并发执行, 执行顺序由内核调度算法来决定。fork()函数如果成功创建了进程, 就会对父子进程各返回一次,其中对父进程返回子进程的 PID,对子进程返回 0;失败则返回小于 0 的错误码。
1.2 终止进程
进程终止可分为正常终止和异常终止两大类, 其中常见的正常终止方式有:
正常终止方式 | 实现方法 |
---|---|
方式-1 | 从 main()函数 return 返回 |
方式-2 | 实现方法调用类 exit()函数 |
异常终止方式 | 实现方法 |
---|---|
方式-1 | 调用 abort()函数 |
方式-2 | 接收到一个信号终止 |
main()函数使用 return 指令返回时会自行调用类 exit()函数来终止进程。异常终止的abort()函数是通过 SIGABRT 信号来实现的。
#include <stdlib.h>
void exit(int status);
参数 status 为进程的退出状态,与 main()函数中 return 的返回值有相同效果,即在程序中:
exit(0);
1.3 exec 族函数
exec 族函数用来修改当前进程的代码空间。 fork()函数的时候提到,在创建进程后子进程与父进程有相同的代码空间;而在实际应用中, 往往需要子进程去执行另外一个程序, 像这种情况,可以在子进程中调用 exec 族函数它的代码段完全替换为新的程序,并从这个新进程的 main 函数开始执行。例如在子进程中执行“/bin/ls”程序:
调用 exec 族函数并不创建进程,因此前后进程的 ID 并无改变, exec 只是用一个全新的程序替换掉当前进程代码空间里的内容。 exec 族函数有 6 个不同的 exec 函数,函数原型分别如下:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
以上函数是在 exec 后面加上如 l、 p、 e、 v 等元素组合成的后缀形成的,这些函数只在参数列表上存在差别
后缀 p:
表示使用 filename 做参数,如果 filename 中包含“/” 则视其为路径名,否则将会在 PATH 环境变量所指定的各个目录中搜索该文件,如 execlp()函数。无后缀 p 则必须使用路径名来指定可执行文件的位置,如 execl()函数。
后缀 e:
表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以 NULL 结束,如 execvpe()函数。而无此后缀的函数则将当前进程的 environ 变量复制给新的程序,如execv()函数。
后缀 l:
表示使用 list 形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec 中给出,最后一个参数需要是 NULL 用以表示没有更多的参数了,如 execl()函数。
后缀 v:
表示使用 vector 形式来传递新程序的参数,传给新程序的所有参数放入一个字符串数组中,数组以 NULL 结束以表示结尾,如 execv()函数。
exec 族函数只有在出错的时候才会返回,如果成功无返回,否则返回-1。
1.4 wait函数
wait()函数用来帮助父进程获取其子进程的退出状态。当进程退出时,内核为每一个进程保存了一定量的退出状态信息,父进程可根据此退出信息来判断子进程的运行状况。如果父进程未调用 wait()函数,则子进程的退出信息将一直保存在内存中。
由于进程终止的异步性,可能会出现子进程先终止或者父进程先终止的情况,从而出现两种特殊的进程:
进程类型 | 描述 |
---|---|
僵尸进程 | 如果子进程先终止,但其父进程没有为它调用 wait()函数, 那么该子进程就会变为僵尸进程。僵尸进程在它的父进程为它调用 wait() 函数之前将一直占有系统的内存资源。 |
孤儿进程 | 如果父进程先终止,尚未终止的子进程将会变成孤儿进程。孤儿进程将 直接被 init 进程收管,由 init 进程负责收集它们的退出状态 |
调用 wait()函数的进程将挂起等待直到它的任一个子进程终止, wait()函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
参数 status 是一个用来保存子进程退出状态的指针, 若函数执行成功,将返回获取到退出状态进程的 PID,否则返回-1。
宏 | 描述 |
---|---|
WIFEXITEX(status) | 若为正常终止子进程返回状态,则为真。对于这种情况可以执行 WEXITSTATUS(status),取自子进程传给 exit, return 参数的低 8 位。 |
WIFSIGNALED(status) | 若为异常终止子进程返回状态,则为真(接收到一个不捕捉的信号)。对于这种情 况可以执行 WTERMSIG(status)获取使子进程退出的信号 |
1.5 守护进程
守护进程(Daemon) 是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,它不需要用户输入就能运行并提供某种服务。守护进程的父进程是 init 进程,因为它真正的父进程在 fork 出该子进程后就先于该子进程 exit 退出了,所以它是一个由 init 领养的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出(无论是向标准输出设备还是标准错误输出设备的输出) 都需要特殊处理。 Linux 系统的大多数服务器就是通过守护进程实现的,且通常以字母 d 结尾来命名进程 名, 比如 sshd、 xinetd、 crond 等。Linux 系统有多种创建守护进程的方法,其中最常用的是使用 daemon()函数来创建守护进程。
daemon()函数的原型如下 :
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数介绍:
参数 | 描述 |
---|---|
nochdir | 如果传入 0,则 daemon 函数将调用进程的工作目录设置为根目录, 否则保持原有的工作目录不变 |
noclose | 如果传入 0,则 daemon 函数会将标准输入、标准输出、标准错误重 定向到/dev/null 文件中,否则不改变这些文件描述符。 |
该函数如果成功则返回 0,否则返回-1,并设置 errno。
2 使用进程相关API的范例
2.1 创建进程
2.1.1 功能介绍
使用fork()创建进程,并打印进程的ID
2.1.2 编写代码
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : test_process.c
作者 : [email protected]
版本 : V1.0
描述 : process API test
其他 : 无
日志 : 初版V1.0 2024/03/04
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork(); /* 创建进程 */
if (pid == 0) { /* 对子进程返回 0 */
printf("Here is child, my pid = %d, parent's pid = %d\n",
getpid(), getppid()); /* 打印父子进程 PID */
exit(0);
}
else if(pid > 0) { /*对父进程返回子进程 PID */
printf("Here is parent, my pid = %d, child's pid = %d\n", getpid(), pid);
}
else {
perror("fork error\n");
}
return 0;
}
2.1.3 运行结果
2.2 等待退出函数
2.2.1 功能介绍
使用正常和异常两种方式,退出进程。
2.2.2 编写代码
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : test_thread.c
作者 : [email protected]
版本 : V1.0
描述 : process API test
其他 : 无
日志 : 初版V1.0 2024/03/04
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static void print_exit_status(int status)
{
/* 自定义打印子进程退出状态函数 */
if (WIFEXITED(status)) /* 正常退出,打印退出返回值 */
{
printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)){ /* 因信号异常退出,打印引起退出的信号 */
printf("abnormal termination, signal number = %d\n", WTERMSIG(status));
}
else{
printf("other status\n"); /* 其它错误 */
}
}
int main(int argc, char *argv[])
{
pid_t pid;
int status;
if ((pid = fork()) < 0) { /* 创建子进程 */
perror("fork error");
exit(-1);
}
else if (pid == 0) {
exit(7); /* 子进程调用 exit 函数,参数为 7 */
}
if (wait(&status) != pid) { /* 父进程等待子进程退出,并获取退出状态*/
perror("fork error");
exit(-1);
}
print_exit_status(status); /* 打印退出状态信息 */
if ((pid = fork()) < 0) { /* 创建第二个子进程 */
perror("fork error");
exit(-1);
}
else if (pid == 0) {
abort(); /* 子进程调用 abort()函数异常退出 */
}
if (wait(&status) != pid) { /* 父进程等待子进程退出,并获取退出状态*/
perror("fork error");
exit(-1);
}
print_exit_status(status); /* 打印第二个退出状态信息 */
return 0;
}
2.2.3 运行结果
运行程序后,通过log可得,一个进程正常退出,另外一个进程是异常退出。
2.3 使用守护进程
2.3.1 功能介绍
使用 daemon()函数创建守护进程的用法,变为守护进程后程序每60 秒打印当前的时间信息到/tmp/daemon.log 文件中。
2.3.2 编写代码
/***************************************************************
Copyright 2024-2029. All rights reserved.
文件名 : test_thread.c
作者 : [email protected]
版本 : V1.0
描述 : process API test
其他 : 无
日志 : 初版V1.0 2024/03/04
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
int main(void)
{
int fd;
time_t curtime;
if(daemon(0,0) == -1) {
perror("daemon error");
exit(-1);
}
fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT|O_APPEND, 0644);
if (fd < 0 ) {
perror("open error");
exit(-1);
}
while(1) {
curtime = time(0);
char *timestr = asctime(localtime(&curtime));
write(fd, timestr, strlen(timestr));
sleep(60);
}
close(fd);
return 0;
}
2.3.3 运行结果
守护进程执行后将脱离控制台,可用 ps -ef 来查看。 下图的执行结果截图,执行程序将直接返回,并可以/tmp/daemon.log 文件查看到日期时间。
查询log文件内容,使用命令:
cat /tmp/daemon.log
3 参考文献
-
《现代操作系统》
-
《linux/unix系统编程手册》