Linux之应用层看进程
Linux的进程(从应用层看)
说明
- 进程是一个程序的一个执行的过程
- 进程是一个独立的可调度的任务
- 进程是系统资源分配的最小单位
- 进程是动态的;程序是静态的,保存在磁盘上的有序集合
- 进程具有并发性、交互性、独立性、动态性
- 进程有:交互式进程、批处理进程、守护进程
1. 查看所有进程命令
-
查看所有进程的命令
ps aux
- 动态显示进程信息命令
top
2. 进程关系树
列出关系树的命令 pstree
通过关系树可以知道最先启动的进程是 init(PID 1)
3. 程序中获取当前进程的pid
每个进程都有属于自己的pid, 父进程是ppid
1. 函数
pid_t getpid(void);//返回值:用来获取当前进程的PID
pid_t getppid(void);//返回值:parent //用来获取当前进程父进程的PID
-
代码示例
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { printf("my id is %d\n",getpid()); //获取当前进程的PID printf("my parent id is %d\n",getppid());//获取当前父进程的PID return 0; }
4. 进程说明
-
进程PID的最大值
32位 PID 32767
- pid号的顺序
顺序增加
- pid的回收
每次进程结束,系统自动回收PID,虽然回收了PID但是不会立刻使用,当系统所有的PID都用没了,才开始使用使用的PID
- 进程和终端
所有运行在linux终端的进程,他们的父进程都是终端,如果终端结束,终端上的所有进程结束
6. 进程的状态
就绪态、运行态、阻塞态
7. 创建子进程
-
需要fork函数来
include <unistd.h>
pid_t fork(void);
-
功能
用于创建一个子进程,当执行完fork就会出现一个新进程(新进程为子进程吗,原来的进程为父进程)
fork执行完,子进程会复制 父进程所有的东西,并且从fork语句后面开始执行
父进程创建子进程,子进程会copy父进程资源,然后两个进程空间完全独立,
子进程某个变量改变,父进程的不变- 例子
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { fork();//执行完此语句后,创建除了一个子进程, printf("Hello World!!\n"); return 0; }
会打印出来两个 Hello World,分别是父进程和子进程打印的
- 如何判断, 父进程和子进程
通过函数的返回值判断区分是当前是父进程还是子进程
pid_t //函数的返回值
pid_t pid = fork();
//pid == -1 //fork函数执行失败
//pid > 0 //说明为父进程,并且pid的值为子进程的PID
// pid == 0 //说明为子进程
-
代码例子
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork failed");
return -1;
}
else if(pid > 0)//说明为父进程
{
sleep(1);//加上延时,为了让子进程先运行
printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
}
else if(pid == 0)//说明为子进程
{
printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
}
return 0;
}
8. _exit和exit的区别
-
exit函数
-
执行此函数后,结束当前进程(在结束当前进程前会刷新缓存区)
-
头文件和函数原型
include <stdlib.h>
void exit(int status)
-
参数
status是一个整形的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示结束
其他值表示出现了错误
- 例子
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { printf("hello world!!!"); //注意此处没有换行符 exit(0); //执行词语句后,程序结束 printf("hahahahahahaha!!!"); //此语句未被执行 return 0; }
-
-
_exit函数
-
功能
执行此函数后,结束当前进程,//注意:_exit()调用程序之后直接结束程序,不刷新缓存区
-
头文件和函数原型
include <unistd.h>
void _exit(int status);
-
例子
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) { printf("hello world!!!");//注意此处没有换行符 _exit(0); //执行此语句后程序结束,但是没有刷新缓存区,hello world打印输出不成功,程序运行什么也不显示 printf("hahahahahahaha!!!"); //此语句依然未执行 return 0; }
-
9. 父进程不应该比子进程先结束,父进程要负责回收子进程的资源
-
孤儿进程
父进程先结束,子进程未结束,子进程默认会1号进程init(福利院)接管,回收子进程的资源
-
代码示例
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid == -1) { perror("fork failed"); return -1; } else if(pid > 0)//说明为父进程 { printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid); } else if(pid == 0)//说明为子进程 { printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid); } return 0; }
/////打印输出结果
I am parent,myid is 4490 myppid is 3821 pid is 4491
linux@ubuntu:~/0703/prcess$ I am child,myid is 4491 myppid is 1 pid is 0/////发现子进程的父进程ID是1,为init
-
僵尸进程
子进程先结束,父进程未结束,但是父进程未调用wait族函数来回收子进程的资源,此时子进程叫做僵尸进程
-
例子
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid == -1) { perror("fork failed"); return -1; } else if(pid > 0)//说明为父进程 { while(1) //父进程未结束 { ;//父进程开心的坐着自己的事 } } else if(pid == 0)//说明为子进程 { printf("child will over!!!\n"); printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid); exit(0); //子进程结束 } return 0; }
////////////重新开启一个新的终端
linux@ubuntu:~$ ps aux | grep a.out
linux 4550 88.2 0.0 1984 284 pts/2 R+ 22:59 0:30 ./a.out
linux 4551 0.0 0.0 0 0 pts/2 Z+ 22:59 0:00 [a.out]//Z+说明为僵尸状态,zombie,僵尸进程
-
-
如何回收子进程的资源
调用wait族函数
-
wait族函数
-
功能
当父进程执行此函数,父进程阻塞等待子进程结束
父进程执行到wait函数就会阻塞,等待子进程的结束,一旦有一个
-
头文件及函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); wait(NULL); int statu; wait(&statu);
-
函数参数
status是整形指针,指向的对象用来保存子进程退出时的状态
status若是空,表示忽略子进程退出时的状态
status若不为空,表示保存子进程退出时的状态
获取结束信息:
1. WIFEXITED(status) 测子进程是否正常退出(调用exit或者_exit()或者从main返回) 如果是正常退出 返回真 否则返回假 2. WEXITSTATUS(status) 这个宏使用的前提是子进程正常退出 它会获取子进程调用exit中 exit里的参数
-
函数返回值
成功:
pid_t //返回值时结束的那个子进程的id
失败:
-1
-
例子
对获取的子进程的结束状态的验证
status //输出参数,里面保存的是子进程结束时的状态
pid_t //返回值是结束的那个子进程的id
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, const char *argv[]) { int status; pid_t child_id; pid_t pid = fork(); if(pid == -1) { perror("fork failed"); return -1; } else if(pid > 0)//说明为父进程 { // child_id = wait(NULL);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞 child_id = wait(&status);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞 printf("statu is %d\n",status); printf("WIFEXITED(status) is %d WEXITSTATUS(status) is %d\n",WIFEXITED(status),WEXITSTATUS(status)); } else if(pid == 0)//说明为子进程 { int x = 5; sleep(1);//虽然子进程加了延时,为了让父进程先运行,但是父进程执行wait()函数后处于阻塞状态 x /= 0;// 执行此语句后,程序挂掉了 printf("++++++++++++++++++++\n"); exit(5); } return 0; }
-
例子 对阻塞功能和返回值的验证
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t child_id; pid_t pid = fork(); if(pid == -1) { perror("fork failed"); return -1; } else if(pid > 0)//说明为父进程 { child_id = wait(NULL);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞 printf("child_id is %d\n",child_id);//wait函数解除阻塞后的返回值,为刚结束子进程的PID printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid); } else if(pid == 0)//说明为子进程 { sleep(1);//虽然子进程加了延时,为了让父进程先运行,但是父进程执行wait()函数后处于阻塞状态 printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid); } return 0; }
-
-
-
waitpid()函数
-
功能
指定等待某一个子进程
-
头文件和函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options); waitpid(3830,NULL,WNOHANG); //不阻塞 waitpid(3830,NULL,0); //阻塞
-
参数说明
pid_t pid //pid > 0 回收进程的ID
int *status //同wait,用来保存子进程结束的状态的
option:
WNOHANG 表示不阻塞,waitpid不阻塞而立即返回,此时值为0,结束后返回值为结束子进程pid
0 阻塞
-
返回值
0: 已经结束运行的子进程进程号
0: 使用WNOHANG且没有子进程退出时,一直返回0,当子进程结束的时候,返回的是子进程的PID
-1:出错 -
应用举例
waitpid(31111, NULL, 0);
//指定阻塞等待PID 31111 子进程结束
-
例子
#include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if(pid == 0) { sleep(5); exit(0); } else if(pid > 0) { while(1) //循环测试子进程是否退出 { int ret = waitpid(pid,NULL,WNOHANG); //调用waitpid,且父进程不阻塞 if(ret == 0) { puts("child is not exited!!"); //若子进程未退出,则父进程暂停1s sleep(1); } else if(pid == ret) //若发现子进程退出,打印输出相应情况 { printf("child is exited!!\n"); break; } else { printf("some error occured!!\n"); break; } } } return 0; }
-
10. Linux的进程分类
-
前台进程
前台进程占用终端
- 后台进程
后台进程不占用终端
- 切换前后台的快捷键
./a.out & //将程序运行在后台
jobs //查看后台进程的运行情况
fg 1 //将后台进程编号为1的进程拿到前台运行
ctrl + z //将前台进程放到后台并处于停止状态
ctrl + c //结束进程
11. 如何编写守护进程
-
查看守护进程
ps -axj
-
如何创建守护进程
需要五个步骤
- 创建子进程,结束父进程(让其成为孤儿进程)
- 子进程创建新会话(setsid()函数)
- 修改当前目录(chdir(“/tmp”)) //不一定修改为这个目录
- 重置文件权限掩码(umask(0)) //不是必须的
- 关闭子进程从父进程复制过来的文件描述符
-
例子
///////打开一个文件,文件描述符从3开始,0、1、2已经被标准输入、标准输出、标准错误说出占用
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid > 0) { //1.创建一个子进程,结束父进程 exit(0); } else if(pid == 0) { //2.创建一个新的会话,脱离原来会话,进程组,最主要脱离原来的控制终端 setsid(); //3.修改进程当前目录 chdir("/tmp"); //4.重置文件权限掩码 umask(0); //5.关闭文件描述符 int num = getdtablesize(); int i; for(i = 0; i < num; i++) { close(i); } //////守护进程做的事////// int fd = open("hello.txt",O_WRONLY | O_CREAT,0666); while(1) { write(fd,"hello\n",strlen("hello\n")+1); sleep(1); } } return 0; }
-
例子创建守护进程,时间每隔两秒,向mydaemon.txt文件写入系统时间
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <time.h> int main(int argc, const char *argv[]) { pid_t pid = fork(); if(pid > 0) { //1.创建一个子进程,结束父进程 exit(0); } else if(pid == 0) { //2.创建一个新的会话,脱离原来会话,进程组,最主要脱离原来的控制终端 setsid(); //3.修改进程当前目录 chdir("/tmp"); //4.重置文件权限掩码 umask(0); //5.关闭文件描述符 int num = getdtablesize(); int i; for(i = 0; i < num; i++) { close(i); } //////守护进程做的事////// int fd = open("hello.txt",O_WRONLY | O_CREAT | O_TRUNC,0666); time_t rawtime; char buf[100] = { 0 }; while(1) { time(&rawtime); sprintf(buf,"%s",ctime(&rawtime)); write(fd,buf,strlen(buf)); sleep(2); } } return 0; }
12. exec函数族(通过下面的函数可以执行另一个程序)
-
功能
用fork函数创建子进程后,子进程往往要调用一种exec族函数来执行另一个程序。
当进程调用一种exec函数时,该进程完全由新程序替换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段
exec函数族, 一个进程中只会执行一次, 执行成功,原进程退出--新进程开始运行
- 头文件及函数原型
(2)头文件及函数原型 #include <unistd.h> 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[]);
l //list 缩写 希望接收以逗号分隔的参数列表,列表以NULL作为结尾标志
p //PATH 系统会按照PATH环境变量中路径进行查找命令
e //enviroment 缩写 指定当前进程所使用的环境变量
v //vertor 缩写 参数传递为以NULL结尾的字符指针数组
六个函数返回:若出错则为- 1,若成功则不返回
- 例子 execl()
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { execl("/bin/ls","ls","-l",NULL); //因为没有p指定PATH环境变量,所以ls命令需要加上路径 return 0; }
- 例子 execv
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { char *args[] = {"ls","-l",NULL};//注意字符指针数组最后一个元素一定要是NULL execv("/bin/ls",args); return 0; }
- 例子 execvp
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { char *args[] = {"ls","-l",NULL};//注意字符指针数组最后一个元素一定要是NULL execvp("ls",args); return 0; }
- 例子 execle
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { char *envp[] = {"PATH=/tmp","NAME=zhangsan","HOME=haha",NULL}; execle("/usr/bin/env","env",NULL,envp); return 0; }
- 例子 execvpe
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { char *args[] = {"env",NULL}; char *envp[] = {"PATH=/tmp","NAME=zhangsan","HOME=haha",NULL}; execvpe("env",args,envp); return 0; }
- 例子 execlp 经常与多进程组合使用,用一个子进程单独执行execlp程序
#include <stdio.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if(pid > 0) { wait(NULL); printf("I am parent!!!\n"); } else if(pid == 0) { execlp("date","date",NULL); printf("hello world!!!\n");//此语句未被执行 } return 0; }