Linux 进程是正在执行中的程序
1 进程简介
启动进程有两种方式:
-
手工启动
- 前台启动:执行一条命令
- 后台启动:在命令后输入一个
&符号
-
调度启动
- 例如定时执行,输入
at 11:27 12/25/2020,输入命令ls -l,再输入Ctrl + D退出;或者使用相对时间,输入at now +5 min;结果不会在终端回显,可以重定向到文件中查看
- 例如定时执行,输入
Linux 常见进程操作命令
| 命令 | 作用 |
|---|---|
| ps | 查看系统中的进程 |
| top | 动态显示系统中的进程 |
| nice | 按用户执行的优先级运行 |
| renice | 改变正在运行的进程的优先级 |
| kill | 终止进程(包括后台进程) |
| crontab | 用于安装、删除或者列出用于驱动 cron 后台进程的任务 |
| bg | 将挂起的进程放到后台执行 |
| fg | 把后台进程转到前台执行 |
2 进程控制
系统会为每个新创建的进程分配一个唯一的正整数,称为进程标识符(PID),父进程的为(PPID),可以通过 getpid 和 getppid 获取
Linux C 与进程相关的主要函数
| 函数名称 | 功能 |
|---|---|
| getpid | 获取当前进程的进程号 |
| getppid | 获取当前进程的父进程号 |
| exec 函数族 | 在进程中启动另一个进程执行 |
| system | 在进程中开始另一个进程 |
| fork | 从已存在的进程中复制一个新进程 |
| sleep | 令当前进程进入睡眠状态,直到达到指定时间,或者被信号中断 |
| exit | 正常终止进程,并把参数 status 返回给父进程,进程中的所有缓冲区数据会自动写回并关闭未关闭的文件 |
| _exit | 立即终止进程,并把参数 status 返回给父进程 ,并关闭未关闭的文件,不会处理标准I/O缓冲区 |
| wait | 暂停父进程,等待子进程运行完成 |
| waitpid | 暂停父进程,等待子进程运行完成 |
2.1 进程创建
1. fork函数
fork 函数示例(备注:vfork 函数可以保证子进程一定优先于父进程开始执行)
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
pid_t pid;
char *message;
int n;
pid = fork();
if (pid < 0) { // 进程创建失败
perror("fork failed");
exit(1);
} else if (pid == 0) { // 子进程执行
message = "子进程执行";
n = 3;
} else { // 父进程执行
message = "父进程执行";
n = 6;
}
for (; n > 0; n--) {
printf(message); // 父进程与子进程交替打印
sleep(1);
}
return 0;
}
2. exec 函数族
execl --- [把可变参数保存到以NULL结尾的指针函数中] ---> execv
execle --- [把可变参数保存到以NULL结尾的指针函数中] ---> execve
execlp --- [把可变参数保存到以NULL结尾的指针数组中] ---> execvp ---[依次在PATH环境变量指示的各目录中查找该程序] ---> execv --- [使用enviorn所指向的当前环境变量表] --> execve 系统调用
exec 函数族的六个成员函数的语法(头文件`#include <unistd.h>`),省略了`exec`前缀
int l (const char *path, const char *arg, ...) // 列表传递
int v (const char *path, char const *argv[]) // 字符串传递
int le (const char *path, const char *arg, ..., char *const envp[])
int ve (const char *path, char *const argv[], char *const envp[])
int lp (const char *file, const char *arg, ...)
int vp (const char *file, char *const argv[])
2.2 进程终止
有五种方式使进程终止:
-
正常终止
- 在 main 函数内执行 return 语句,等效于调用 exit
- 调用 exit 函数(由ANSI C定义),其操作包括调用各终止处理程序,然后关闭所有标准I/O流等
-
异常终止
- 调用 abort
- 由一个信号终止
2.3 僵尸进程
一个已经终止运行、但其父进程尚未对其善后处理(获取终止子进程的有关信息、释放它仍然占用的资源)的进程被称为僵尸进程(zombie),ps 将僵尸进程的状态显示为Z
可以在父进程中调用 wait 或 waitpid 函数,使子进程比父进程早终止,而让父进程有机会了解子进程终止时的状态
- wait 用于使父进程阻塞,直到一个子进程终止或者该进程接到了一个指定的信号为止
- waitpid 有若干选项,也能支持作业控制,wait 只是 waitpid 的一个特例
init 进程是Linux启动后创建的第一个进程,当一个程序退出,它的子进程会被 init 进程继承
2.4 守护进程
守护进程最重要的特性是后台运行,其次守护进程必须与其运行前的环境隔离开来(包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码),可以通过 /etc/rc.d、crond 进程或用户终端启动
输入 ps -aux后:
- init 系统守护进程,PID 为 1,负责启动各层次特定的系统服务,这些服务通常是由它们自身的守护进程实现的
- keventd 守护进程,为在内核中运行计划执行的函数提供进程上下文
- kswapd 守护进程,页面置换守护进程
- bdflush 和 kupdated 守护进程,Linux内核使用
- portmap 端口映射守护进程,提供将RPC程序号映射为网络端口号的服务
- syslogd 守护进程,可帮助操作人员把系统消息记入日志
- inetd 守护进程(xinetd),监听系统网络接口,以便取得来自网络的对各种网络服务进程的请求
- nfsd,lockd,rpciod 守护进程,提供对网络文件系统(NFS)的支持
- cupsd 守护进程,打印脱机进程,处理对系统提出的所有打印请求
编写守护进程的要点:
- 创建子进程,终止父进程
- 在子进程中创建新会话(例如setsid)
- 改变工作目录(chdir)
- 重设文件掩码(unmask(0))
- 关闭文件描述符(遍历NOFILE,close(i))
void init_daemon(void);
int main() {
FILE *fp;
time_t t;
init_daemon();
while (1) {
sleep(10);
if (fp=fopen("xxx.log") >= 0) {
t = time(0);
fprintf(fp, "%s", asctime(localtime(&t)));
fclose(fp);
}
}
}
void init_daemon(void) {
pid_t child1, child2;
int i;
child1 = fork();
if (child1 > 0) {
exit(0); // 1. 创建子进程,终止父进程
} else if (child < 0) {
perror("创建子进程失败");
exit(1);
}
setsid(); // 2. 在子进程中创建新会话
chdir("/tmp"); // 3. 改变工作目录到 /tmp
umask(0); // 4. 重设文件创建掩码
for(i = 0; i < NOFILE; ++i) {
close(i); // 5. 关闭文件描述符
}
return;
}