2021-03-01 Linux 进程控制

149 阅读5分钟

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.dcrond 进程或用户终端启动

输入 ps -aux后:

  • init 系统守护进程,PID 为 1,负责启动各层次特定的系统服务,这些服务通常是由它们自身的守护进程实现的
  • keventd 守护进程,为在内核中运行计划执行的函数提供进程上下文
  • kswapd 守护进程,页面置换守护进程
  • bdflush 和 kupdated 守护进程,Linux内核使用
  • portmap 端口映射守护进程,提供将RPC程序号映射为网络端口号的服务
  • syslogd 守护进程,可帮助操作人员把系统消息记入日志
  • inetd 守护进程(xinetd),监听系统网络接口,以便取得来自网络的对各种网络服务进程的请求
  • nfsd,lockd,rpciod 守护进程,提供对网络文件系统(NFS)的支持
  • cupsd 守护进程,打印脱机进程,处理对系统提出的所有打印请求

编写守护进程的要点:

  1. 创建子进程,终止父进程
  2. 在子进程中创建新会话(例如setsid)
  3. 改变工作目录(chdir)
  4. 重设文件掩码(unmask(0))
  5. 关闭文件描述符(遍历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;
}