五、如何获取pid和ppid
我们想要获取到pid和ppid,就要用到系统调用接口:
pid_t getpid( void ) --- 返回的是子进程ID
pid_t getppid( void ) --- 返回的是父进程ID
我们可以通过 man指令对这些函数进行详细说明的查看
1.getpid() --- 获取子进程(pid)
#include<stdio.h> #include<unistd.h> int main() { printf("I am child pid: %d\n",getpid()); return 0; }
2.getppid() --- 获取父进程(ppid)
#include<stdio.h> #include<unistd.h> int main() { printf("I am father pid: %d\n",getppid()); return 0; }
这里将两个代码整合到一起后,通过死循环不断的打印有父子进程的ID,并对进程进程检测,发现ps检测到的ID和系统接口获取到的ID是一样的。
六、进程的创建 --- fork初识
1.四种主要事件会导致进程的创建
1.系统初始化
2.正在运行的程序执行了创建程序的系统调用
3.用户请求创建一个新进程
4.一个批处理作业的初始化
2.用户如何请求创建一个新进程
通过fork函数来进行进程的创建,我们可以man fork查看相关的函数信息。这是一个系统调用,它会创建一个与调用进程相同的副本。在调用fork之后,这两个进程(父进程和子进程)拥有相同的内存映射。
请看下面这段代码,我们在执行循环前创建了一个子进程,会有什么样的效果呢?
#include<stdio.h> #include<unistd.h> int main() { fork();//创建子进程 while(1){ printf("I am child pid:%d I am father ppid:%d\n",getpid(),getppid()); sleep(1); } return 0; }
从上述的结果可以看出,main函数的进程和fork创建的进程打印的结果是一样的,并且通过pid和ppid发现,fork的父进程就是main函数的进程,说明fork所创建出来的子进程和父进程在内存上映射。
3. 如何让父子进程各有所需
以上创建的子进程所做的事和父进程是一样的,显然意义并不大,我们要能够让所创建的子进程做和父进程不一样的事,才是我们想要的。那如何实现呢?
首先,对于fork是有两个返回值的
1、如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
2、如果子进程创建失败,则在父进程中返回 -1。
#include <stdio.h> #include <unistd.h> int main() { int ret = fork(); if(ret == 0){ //如果子进程创建成功,给子进程返回0 while(1){ printf("I am child!\n"); sleep(1); } } else if(ret > 0){ //如果子进程创建成功,给父进程返回子进程的pid while(1){ printf("I am father!\n"); sleep(1); } } else{ //如果子进程创建失败,给父进程返回-1 //fork error } return 0; }
七、进程的状态
1.进程状态有哪些
CPU对进程处理,取决于进程当前进程所处的状态,CPU对于不同状态的进程会采取不同的措施。
/* * The task state array is a strange "bitmap" of * reasons to sleep. Thus "running" is zero, and * you can test for combinations of others with * simple bit tests. */ static const char * const task_state_array[] = { "R (running)", /* 0 */ //不可中断 "S (sleeping)", /* 1 */ //正在运行,或在队列中的进程 "D (disk sleep)", /* 2 */ //处于休眠状态 "T (stopped)", /* 4 */ //停止或被追踪 "t (tracing stop)", /* 8 */ //追踪状态,类似于vs下打断点后直接运行到断点处 "X (dead)", /* 16 */ //死掉的进程 "Z (zombie)", /* 32 */ //僵尸进程 };
2.进程状态的查看
ps axj / ps aux
3.进程状态的分析
1.运行状态 --- R
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
上图想表达的意思是,进程A处于运行中,在一段时间后,就会切换到进程B....,这个时间很快,CPU运行这些进程是采用了时间轮转调度算法。在时间片轮转调度算法中,系统根据先来先服务的原则,将所有的就绪进程排成一个就绪队列,并且每隔一段时间产生一次中断,激活系统中的进程调度程序,完成一次处理机调度,把处理机分配给就绪队列队首进程,让其执行指令。当时间片结束或进程执行结束,系统再次将cpu分配给队首进程。
2.浅度睡眠状态 --- S
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
#include <stdio.h> #include <unuistd.h> int main() { printf("hello linux!\n"); sleep(50); return 0; }
处于S状态的进程,是可以被立即终止的
3.深度睡眠状态 --- D
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。例如,当进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)
4.停止状态 --- T
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
#include <stdio.h> #include <unuistd.h> int main() { while(1) { printf("hello linux!\n"); sleep(1); } return 0; }
在上述程序跑起来之后,处于浅度睡眠状态,我们可以发送 SIGSTOP 信号给进程来停止(T)进程;
我们发送 SIGSTOP 信号给进程来停止(T)进程后,还可以发送SIGCONT 信号让进程继续运行。
查看kill相关信号:
kill -l
以上的信号,可以查看教程后,尝试尝试
5.僵死状态 --- Z
僵死状态(Zombies)是一个比较特殊的状态。当子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程,僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态;
6.死亡状态 --- X
X死亡状态(dead):死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态。
八、僵尸进程与孤儿进程
1.僵尸进程的危害
有如下代码,我们执行之后,子进程会不断的打印数据,父进程等待子进程的过程中,我们立刻杀掉子进程,那么子进程就会处于僵尸状态,而此时程序还在执行,父进程在等待子进程退出的状态,我们把这种进程称之为僵尸进程。
#include <stdio.h> #include <unistd.h> int main() { int ret = fork(); if(ret == 0){//子进程一直打印 while(1){ printf("hello linux!\n"); sleep(1); } } else{//父进程什么都不干,就睡觉 sleep(100); } return 0; }
僵尸进程的危害:
1.进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?
是的!
2.维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?
是的!
3.那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?
是的! 因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
4.内存泄漏?
是的!
2.孤儿进程
刚刚提到了僵尸进程是由于子进程先退出而父进程没有对子进程的退出信息进行读取;那么父进程先退出,子进程在进入僵尸状态后,其父进程未能对其做出处理,那么就称该进程是孤儿进程。若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t ret = fork(); if(ret == 0){//子进程不断的打印数据 while(1){ printf("I am child, running!\n"); sleep(1); } } else{//父进程打印数据,休眠10秒后,直接退出 printf("father do nothing!\n"); sleep(10); exit(1); } return 0; }