注:
- 进程 process: 是os的最小单元os会为每个进程分配大小为4g的虚拟内存空间, 其中 1g 给内核空间3g给用户空间{ 代码区 数据区 堆栈区}
- ps –elf 查看所有的进程 ps –elLf 查看线程
- ps -elf | grep 'aa’查找指定(aa) 进程
- ps -aux 看%cpu(cpu 使用量) %mem( 内存使用量) stat 状态{ S 睡眠 T 暂停 R 运行 Z 僵尸}
- [bg 作业 ID]可以将该进程带入后台运行
- 利用 jobs 可以查看后台任务
- fg 1 把后台任务带到前台, 这里的 1 表示作业 ID
- kill -9 进程号表示向某个进程发送 9 号信号, 从而杀掉某个进程
- renice 是改变优先级的 进程优先级有140个,从-40到99
- 优先级默认值越低,其优先级越高。
2、进程的创建
Linux 下有四类创建子进程的函数: system(), fork(), exec*(), popen()
-
system 函数 (也可以用来嵌脚本)
-
- #include <stdlib.h>
- int system(const char *string);
- system 函数通过调用 shell 程序/bin/sh –c 来执行 string 所指定的命令, 该函数在内部是通过调用execve(“/bin/sh”,…)函数来实现的。 通过 system 创建子进程后, 原进程和子进程各自运行, 相互间关联较少。 如果 system 调用成功, 将返回 0。
- #include <stdlib.h>
-
fork 函数 (非常重要)
-
- #include <unistd.h>
- pid_t fork(void);
- 它从已存在进程中创建一个新进程。 新进程为子进程,而原进程为父进程。 它和其他函数的区别在于: 它执行一次返回两个值。其中父进程的返回值是子进程的进程号,而子进程的返回值为0.若出错则返回-1.因此可以通过返回值来判断是父进程还是子进程。
- **注意:**使用 fork 函数得到的子进程是父进程的一个复制品, 它从父进程继承了进程的地址空间, 包括进程上下文、 进程堆栈、 内存信息、 打开的文件描述符、 信号控制设定、 进程优先级、 进程组号、 当前工作目录、 根目录、 资源限制、 控制终端,而子进程所独有的只有它的进程号、资源使用和计时器等。 通过这种复制方式创建出子进程后, 原有进程和子进程都从函数 fork 返回,各自继续往下运行, 但是原进程的 fork 返回值与子进程的 fork 返回值不同,在原进程中,fork返回子进程的pid,而在子进程中, fork 返回 0,如果 fork 返回负值, 表示创建子进程失败
- #include <unistd.h>
-
exec 函数族
-
- exec*由一组函数组成
- int execl(const char *path, const char *arg, …)
- exec 函数族的工作过程与 fork 完全不同, fork 是在复制一份原进程, 而 exec 函数是用 exec 的第一个参数指定的程序覆盖现有进程空间( 也就是说执行 exec 族函数之后, 它后面的所有代码不在执行)。
- path 是包括执行文件名的全路径名
- arg 是可执行文件的命令行参数, 多个用, 分割注意最后一个参数必须为 NULL。
- exec*由一组函数组成
-
popen 函数类似于 system 函数, 与 system 的不同之处在于它使用管道工作
-
- #include <stdio.h>
- FILE *popen(const char *command, const char *type);
- int pclose(FILE *stream);
- command 为可执行文件的全路径和执行参数;
- type 可选参数为”r”或”w”,如果为”w”,则 popen 返回的文件流做为新进程的标准输入流,
- 即 stdin,如果为”r”,则 popen返回的文件流做为新进程的标准输出流。
- pclose 等待新进程的结束, 而不是杀新进程。
- #include <stdio.h>
3、进程的控制
- 如果父进程先于子进程退出, 则子进程成为孤儿进程, 此时将自动被 PID 为 1 的进程( 即 init) 接管。 孤儿进程退出后, 它的清理工作有祖先进程 init 自动处理。
- 如果子进程先退出, 系统不会自动清理掉子进程的环境, 而必须由父进程调用 wait 或waitpid 函数来完成清理工作, 如果父进程不做清理工作, 则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸( zombie) 进程过多, 将会影响系统的性能, 所以必须对僵尸进程进行处理。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait 和 waitpid 都将暂停父进程, 等待一个已经退出的子进程, 并进行清理工作;
wait 函数随机地等待一个已经退出的子进程, 并返回该子进程的 pid;
waitpid 等待指定 pid 的子进程; 如果为-1 表示等待所有子进程。
status 参数是传出参数, 存放子进程的退出状态;
通常用下面的两个宏来获取状态信息:
WIFEXITED(status) 如果子进程正常结束, 它就取一个非 0 值。 传入整型值,非地址
WEXITSTATUS(status) 如果 WIFEXITED 非零, 它返回子进程的退出码
options 用于改变 waitpid 的行为, 其中最常用的是 WNOHANG, 它表示无论子进程是
否退出都将立即返回, 不会将调用者的执行挂起。
eg:
if(WIFEXITED(status))
{
printf(“the child exit value=%d\n”,WEXITSTATUS(status));
}else{
printf(“child crash\n”);
}
4、进程的终止
进程的终止有 5 种方式:
- main 函数的自然返回;
- 调用 exit 函数
- 调用_exit 函数
- 调用 abort 函数
- 接收到能导致进程终止的信号 ctrl+c SIGINT ctrl+\ SIGQUIT
前 3 种方式为正常的终止, 后 2 种为非正常终止。 但是无论哪种方式, 进程终止时都
将执行相同的关闭打开的文件, 释放占用的内存等资源。
exit 和_exit 函数都是用来终止进程的。 当程序执行到 exit 和_exit 时, 进程会无条件
的停止剩下的所有操作, 清除包括 PCB 在内的各种数据结构, 并终止本程序的运行。
但是它们是有区别的。
exit 函数和_exit函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件, 就是图中的“ 清理 I/O 缓冲” 。比如有一些数据, 认为已经写入文件,实际上因为没有满足特定的条件,它们还只是
保存在缓冲区内, 这时用_exit 函数直接将进程关闭,缓冲区中的数据就会丢失。
- exit 和_exit 函数的原型:
- #include <stdlib.h> //exit 的头文件
- #include <unistd.h> //_exit 的头文件
- void exit(int status);
- void _exit(int status);
- status 是一个整型的参数, 可以利用这个参数传递进程结束时的状态。 一般来说, 0 表示正常结束; 其他的数值表示出现了错误,进程非正常结束。
5、用 fork 继承打开的文件
fork 以后的子进程自动继承了父进程的打开的文件, 继承以后, 父进程关闭打开的文
件不会对子进
程造成影响。相当于引用计数加1.
6、守护进程
Daemon 运行在后台也称作“后台服务进程”
- 在 linux中,每一个系统与用户进行交流的界面称为终端, 每一个从此终端开始运行的进程都会依赖这个终端, 这个终端就称为这些进程的控制终端。 当控制终端被关闭时, 相应的进程都会自动关闭。
- 但是守护进程却能突破这种限制, 它被执行开始运转, 直到整个系统关闭时才退出。几乎所有的服务器程序如 Apache 和 wu-FTP, 都用 daemon 进程的形式实现。
- 很多 Linux下常见的命令如 inetd 和 ftpd, 末尾的字母 d 通常就是指 daemon。守护进程的特性
daemon 进程的创建编程规则
- 创建子进程, 父进程退出,变成一个孤儿进程( orphan) 。
pid = fork();
if(pid>0)
exit(0);
- 在子进程中创建新会话:使用系统函数 setsid()。
- 由于创建守护进程的第一步调用了 fork 函数来创建子进程, 再将父进程退出。 由于在调用 fork 函数的时候, 子进程全盘拷贝了父进程的会话期、 进程组、控制终端等, 虽然父进程退出了, 但会话期、 进程组、 控制终端并没有改变, 因此, 还不是真正意义上的独立开来。 而调用 setsid 函数会创建一个新的会话并自任该会话的组长, 调用 setsid 函数有下面 3 个作用: 让进程摆脱原会话的控制, 让进程摆脱原进程组的控制, 让进程摆脱原控制终端的控制。进程组: 是一个或多个进程的集合。 进程组有进程组 ID 来唯一标识。 除了进程号(PID)之外, 进程组 ID( GID) 也是一个进程的必备属性。 每个进程都有一个组长进程, 其组长进程的进程号等于进程组 ID。 且该进程组 ID 不会因为组长进程的退出而受影响。
- 会话周期: 会话期是一个或多个进程组的集合。 通常, 一个会话开始于用户登录, 终止于用户退出, 在此期间该用户运行的所有进程都属于这个会话期。
- 控制终端: 由于在 linux 中, 每一个系统与用户进行交流的界面称为终端, 每一个从此终端开始运行的进程都会依赖这个控制终端。
- 改变当前目录为根目录:
- 使用 fork 函数创建的子进程继承了父进程的当前工作目录。 由于在进程运行中, 当前目录所在的文件是不能卸载的, 这对以后的使用会造成很多的不便。 利用 chdir(“/”);把当前工作目录切换到根目录
- 重设文件权限掩码:
- umask(0);将文件权限掩码设为 0,Deamon 创建文件不会有太大麻烦;
- 关闭所有不需要的文件描述符
- 新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写, 而它们一直消耗系统资源。通常关闭从 0 到 MAXFILE 的所有文件描述符。
- for(i=0;i<MAXFILE;i++)
- close(i);
- ( 注: 有时还要处理 SIGCHLD 信号 signal(SIGCHLD, SIG_IGN);防止僵尸进程( zombie) )
daemon小例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
void Daemon()
{
const int MAXFD=64;
int i=0;
if(fork()!=0)//父进程退出
exit(0);
setsid(); //成为新进程组组长和新会话领导, 脱离控制终端
chdir(“/”); //设置工作目录为根目录
umask(0); //重设文件访问权限掩码
for(;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件
close(i);
}
i
nt main()
{
Daemon(); //成为守护进程
while(1)
{