-
本文是我在学习进程时总结的一些笔记,后续还会更新,我会把我遇到的一些问题也会提出来,也会对一些知识点进行总结,或者以画图方式进行理解,如果你也在学习进程知识,可以拿以参考。如果过程中发现我的错误,请务必私信我哈,非常感谢!!!
-
加油,屏幕对面的你
!!!这块笔记来自b站一个老师的视频,老师讲的非常有思路,告诉你进程从出生到牺牲都经历了什么点我跳转!!!
进程在Linux中的数据结构资源分配的单位(搞清楚进程的资源,就搞清楚了进程)
PCB(进程控制块)
- pstree 树形式,显示进程关系
- Linux中用三种不同数据结构来描述它,在不同场景都能更高效(空间换时间)
僵尸
僵尸、僵尸的清除
#include#include #include #include int main(void) { pid_t pid,wait_pid; int status; pid = fork(); if (pid==-1) { perror("Cannot create new process"); exit(1); } else if (pid==0) { printf("child process id: %ldn", (long) getpid()); pause(); _exit(0); } else { #if 0 printf("ppid:%dn", getpid()); while(1); #endif do { wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED); if (wait_pid == -1) { perror("cannot using waitpid function"); exit(1); } if (WIFEXITED(status)) printf("child process exites, status=%dn", WEXITSTATUS(status)); if(WIFSIGNALED(status)) printf("child process is killed by signal %dn", WTERMSIG(status)); if (WIFSTOPPED(status)) printf("child process is stopped by signal %dn", WSTOPSIG(status)); if (WIFCONTINUED(status)) printf("child process resume running....n"); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); exit(0); } }
- 子进程死后,父进程收尸(#if 0)
- 子进程死后,父进程不给予处理(#uf 1)
执行 ps aux 查看父、子进程情况
这时候,把子进程的父进程杀死,僵尸就没了(原因往后看)
再执行ps aux父、子进程就都没了
暂停和继续进程
#includeint main(int argc, const char *argv[]) { int i=0; while(1){ volatile j; for (i=0; i < 100000; i++){ printf("666 %dn", j++); } } return 0; }
- Ctrl +C 、killall a.out
杀掉进程a.out - Ctrl + Z 暂停
- fg(front) 进程放在前台继续执行
- bg (back) 进程放在后台继续执行
- 点我了解前边三个命令
- 后台进程的终止
方法一:
通过jobs命令查看job号(假设为num),然后执行kill %num
方法二:
通过ps命令查看job的进程号(PID,假设为pid),然后执行kill pid
前台进程的终止:
ctrl+c
kill的其他作用
kill除了可以终止进程,还能给进程发送其它信号,使用kill -l 可以察看kill支持的信号。
- cpulimit -l 10 -p pid号 把某个进程的CPU利用率限制为10%
- 问题
执行a.out,后cpu利用率总是在3%左右,但是程序在飞快运行。执行cpulimit依然没效果
睡眠
- 深度睡眠视频里会说
- 点我查看进程管理笔记
- 课程同步笔记
查看系统中所有进程
- ps aux
查看系统中所有进程,使用BSD操作系统格式。(Unix) - ps -le
查看系统中所有进程,使用Linux标准命令格式。
ps aux 输出信息
查看系统健康状态
- -d 秒数: 指定top命令每隔几秒更新。默认是3秒 在top命令的交互模式当中可以执行的命令
- ?或h: 显示交互模式的帮助
- P: 以CPU使用率排序,默认就是此项
- M: 以内存的使用率排序
- N: 以PID排序
- q: 退出top
查看、修改进程优先级
| 命令 | 作用 |
|---|---|
| nice | 按用户指定的优先级,运行进程 |
| renice | 改变正在运行进程的优先级 |
-
注意:
进程的nice值默认是0,范围为- 20~20,nice值越小则优先级越高。普通用户设置的nice值只能为0 ~ 20,且只能增加nice值 -
nice的应用
先创建一个睡眠的进程
#include#include #include int main(){ printf("Hi bro!!n"); while(1){ usleep(100000); } _exit(0); }
然后
linux@linux:~/Lesson/level5/day1$ gcc -o nice nice.c //1.编译
linux@linux:~/Lesson/level5/day1$ nice -n 19 ./nice //2.修改优先级后运行
Hi bro!!
^C
linux@linux:~/Lesson/level5/day1$ ps -ef|grep nice //3.查看PID(进程的ID)
linux 18325 18036 0 06:23 pts/0 00:00:00 grep --color=auto nice
linux@linux:~/Lesson/level5/day1$ top -p 18325 //4.根据PID查看它的NI(优先级)是否修改
问题
进程相关函数 fork
- 修改优先级这块我的现象不对,需要问老师
示例
#includeexit/_exit/return#include #include int main(void) { pid_t pid,wait_pid; int status; pid = fork(); if (pid==-1) { perror("Cannot create new process"); exit(1); } else if (pid==0) { printf("an"); } else { printf("bn"); } printf("cn"); while(1); }
描述
C 库函数 void exit(int status) 立即终止调用进程。任何属于该进程的打开的文件描述符都会被关闭,该进程的子进程由进程 1 继承,初始化,且会向父进程发送一个 SIGCHLD 信号。
函数原型
#include#include void exit(int status);//用stdlib.h头文件 void _exit(int status);//用unistd.h头文件
- exit结束后会刷新流的缓冲区
参数
- status – 返回给父进程的状态值。
返回值
- 该函数不返回值。
示例
#include#include int main () { printf("程序的开头....n"); printf("退出程序....n"); exit(0); printf("程序的结尾....n"); return(0); }
exec程序的开头....
退出程序....
exec族函数函数的作用
- 我们用fork函数创建子进程后,由于子进程和父进程的内容一样,没必要存在两个一样的,所以我们经常会在子进程中调用exec函数去执行另外一个程序。
- 当进程调用exec函数时,子进程被完全替换为新程序。
- 因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec族函数定义
功能:
- 在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
函数原型:
#includeint execl(const char *path,const *arg,...);//arg...传递给执行的程序的参数列表 int execlp(const char *file,const char *arg,...);//arg...传递给执行的程序的参数列表 int execv(const char *path,char *const argv[]);//arg...封装成指针数组的形式 int execvp(const char*file,char *const argv[]);//arg...封装成指针数组的形式
参数:
- path:可执行文件的路径名字
- arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
- file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
返回值:
- exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
示例
执行ls命令,显示/etc目录下所有文件的详细信息
方式1:execl
if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0{//一定要以NULL结尾,并判断函数是否执行成功
perror("execl");
}
方式2:execlp
if(execlp("ls","ls","-a","-l","/etc",NULL)<0{//会自动在PATH路劲搜索
perror("execlp");
}
方式3:execv
char *arg[]={"ls","-a","-l","/etc",NULL};//将要传递的参数放在数组中
if(execv("/bin/ls",arg)<0){
perror("execv");
}
方式3:execvp
if(execvp("ls",arg)<0){
perror("execvp");//会自动在PATH路径中搜索
}
system
函数原型
#includeint system(const char *command)
返回值:
- 成功时返回command的返回值;失败时返回EOF(自动创建一个子进程,子进程执行命令)
- 当前进程(父进程)等待command执行结束后才继续执行。
示例
#include#include int main(){ system("ls -a -l ./"); printf("Hello!n"); return 0; }
wait/waitpidlinux@linux:~/Lesson/level5/day2$ gcc system.c
linux@linux:~/Lesson/level5/day2$ ./a.out
total 24
drwxrwxr-x 2 linux linux 4096 Oct 10 09:29 .
drwxrwxr-x 4 linux linux 4096 Oct 10 08:49 ..
-rwxrwxr-x 1 linux linux 7331 Oct 10 09:29 a.out
-rw-rw-r-- 1 linux linux 480 Oct 10 09:29 exec.c
-rw-rw-r-- 1 linux linux 130 Oct 10 09:29 system.c
Hello!
功能
- 回收进程
- 子进程结束时由父进程回收
- 孤儿进程由init进程回收
- 若孤儿进程没有回收会出现僵尸进程
wait 函数原型
#includepid_t wait(int *status);
参数
- status指定保存子进程返回值和结束方式的地址
返回值:
-
成功时返回回收子进程的进程号;失败时返回EOF
-
若子进程没有结束,父进程一直阻塞
-
若有多个子进程,那个进程先结束就先回收
-
status为NULL表示直接释放子进程PCB,不接收返回值
示例
#include#include #include int main() { int status; pid_t pid, pr; pid = fork(); if (pid < 0){ perror("fork"); exit(-1); } else if(pid == 0){ sleep(1); exit(1); } else{ pr = wait(NULL); //不用&status,不关心程序怎么结束的 printf("process %d is over!n", pr); } return 0; }
| 宏标识符 | 作用 |
|---|---|
| WIFEXITED(status) | 判断子进程是否正常结束 |
| WEXITSTATUS(status) | 获得子进程的返回值 |
| WIFSIGNALED(status) | 判断子进程是否被信号结束 |
| WTERMSIG(status) | 获取结束子进程的信号类型 |
- 父进程用宏标识符,通过status判断子进程的状态
示例
#include#include #include int main(int argc, const char *argv[]) { int status; pid_t pid, pr; pid = fork(); if (pid < 0){ perror("fork"); exit(-1); }else if (pid == 0){ printf("the PID of son process is %dn", getpid()); sleep(1); while(1); //让父进程一直等待信号,此时子进程处于僵尸态 // exit(2); }else{ #if 0 printf("the PID of father process is %dn", getpid()); while(1); #endif pr = wait(&status); if (pr == -1){ perror("con't use wait functionn"); exit(1); } if (WIFEXITED(status)){ //子进程是否正常结束 printf("%d is finished normally, status = %dn", pr, WEXITSTATUS(status)); } else if(WIFSIGNALED(status)){ //子进程是否被信号结束 printf("%d is killed by signal %dn", pr, WTERMSIG(status)); } exit(0); } return 0; }
总结
exit 函数主要就是把进程结束后(释放资源后),向父进程发送一个 SIGCHLD 信号
这时父进程里正在阻塞的wait 会获取到这个信号,把僵尸进程(释放资源后的进程) 的PCB释放,使得进程彻底消失
status 是父进程从子进程捕捉到的子进程的状态----子进程是如何结束的,一般我们只需让进程消失就行了,不用管它的状态
waitpid函数原型
#include#include pid_t waitpid(pid_t pid,int *status,int options)
- 从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。
参数
- pid:从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。
- pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
- pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
- pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
- pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
- options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:
ret=waitpid(-1,NULL,0);
- WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
- WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。
返回值
- 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
示例
#include守护进程 概念#include #include #include int main(){ pid_t pc, pr; pc = fork(); if (pc < 0){ error("fork"); exit(-1); }else if (pc == 0){ sleep(3); exit(0); } do{ pr = waitpid(pc, NULL, WNOHANG); //没等到信号过来,返回0 if (pr == 0){ printf("Not receive a signal form a childn"); sleep(1); } }while(pr == 0); //如果信号一直没收到,就一直循环 if (pr == pc){ printf("successfully get a child's signaln"); }else{ printf("Errorn"); } return 0; }
守护进程(Daemon进程)是Linux中的后台服务进程。
特点
- 生存期长
- 独立于控制终端
- 周期性执行某种任务或等待处理某些发生的事件
- 系统启动时开始执行,关闭时终止
作用
当你希望某个进程不因为用户、终端或者其他的变化而受影响,你要把它变成守护进程
终端是什么
终端是每个系统与用户进行交流的界面
作用
守护进程的创建
- 进程从一个终端开始运行,它就会依附于这个终端。
- 这个终端又称为这些进程的“控制终端”
- 控制终端关闭时,相应的进程都会自动结束
- 创建子进程,父进程退出
if(fork()>0){
exit(0);
}
- 子进程变成孤儿进程,被init进程收养
- 子进程在后台运行,但是依旧和终端相关联
- 子进程创建新会话
if(setsid()<0)//通过setsid创建一个新的会话
{
exit(-1);
}
- 子进程成为新的会话组长
- 子进程脱离原先的终端
- 更改当前工作目录
chdir("/");
chdir("/tmp");//所有用户可读可写可执行
- 守护进程一直在后台运行,其工作目录不能被卸载
- 重新设定当前工作目录cwd
- 重设文件权限掩码
if(umask(0)<0){
exit(-1);
}
- 文件权限掩码设置为0
- 只影响当前进程
- 关闭打开的文件描述符
int i; for(i=0;i
- 关闭所有从父进程继承的打开文件
- 已脱离终端,stdin/stdout/stderr无法再使用
示例:
#include进程创建的写时拷贝技术 Copy-On-Write#include #include #include #include #include #include int main(int argc, const char *argv[]){ pid_t pid; int i,fd; char *buf = "This is a Daemonn"; pid = fork(); if (pid < 0){ printf("Error forkn"); exit(1); }else if (pid > 0){ exit(0); // parent out } setsid(); chdir("/tmp"); umask(0); for (i=0; i < getdtablesize(); i++){ close(i); } if ((fd = open("daemon.log", O_CREAT|O_WRONLY|O_TRUNC, 0600)) < 0){ printf("Open file errorn"); exit(1); } while(1){ write(fd, buf, strlen(buf)); sleep(2); } exit(0); } #include#include #include #include int data = 10; int child_process() { printf("Child process %d, data %dn",getpid(),data); data = 20; printf("Child process %d, data %dn",getpid(),data); _exit(0); } int main(int argc, char* argv[]) { int pid; pid = fork(); if(pid==0) { child_process(); } else{ sleep(1); printf("Parent process %d, data %dn",getpid(), data); exit(0); } } vforklinux@linux:~/Lesson/level5/day3$ gcc cow.c
linux@linux:~/Lesson/level5/day3$ ./a.out
Child process 4701, data 10
Child process 4701, data 20
Parent process 4700, data 10#include#include #include int data = 10; int child_process() { printf("Child process %d, data %dn",getpid(),data); data = 20; printf("Child process %d, data %dn",getpid(),data); _exit(0); } int main(int argc, char* argv[]) { if(vfork()==0) { child_process(); } else{ sleep(1); printf("Parent process %d, data %dn",getpid(), data); } } 线程linux@linux:~/Lesson/level5/day3$ gcc vfork.c
linux@linux:~/Lesson/level5/day3$ ./a.out
Child process 4858, data 10
Child process 4858, data 20
Parent process 4857, data 20PID和TGID



