创建子进程
1.fork
#include <unistd.h> //头文件
pid_t fork(void) //原型
如:
#include <stdio.h>
#include <unistd.h>
int main(){
printf("输出一遍");
fork();
sleep(1);
printf("输出两遍: pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
return 0;
}
在fork之后将会有两个子进程。
一个是原进程执行下来叫父进程,
一个是fork之后创建的子进程
fork有两个返回值
给父进程返回子进程的PID,
调用成功给子进程返回0值,调用失败,给子进程返回-1
一般多进程的使用
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t ret=fork();
if(ret > 0){
//parent
}
else if(ret == 0){
//child
}
else{
printf("fork error\n");
}
sleep(1);
return 0;
}
此时ret>0的情况与ret==0的两个不同判断语句的执行流都会执行,都会打印,在原来的单进程中不会都打印,在多进程中可以都打印
父进程与子进程如果是死循环,那也是两个死循环打印
父子进程代码共享,数据各自开辟空间,私有一份
2.关于fork的问题
如何理解进程创建?
创建进程是系统中多了一个进程,多了一个进程,就多了一个进程系统,就要多一组(管理进程的数据结构+该进程对应的代码和数据)
管理进程的数据结构我们现在只讲了task_struct,但实际创建一个进程需很多数据结构,代价是很高的
fork父子进程执行顺序和代码和数据复制问题,进程数据=代码+数据
1.父进程创建子进程的时候,代码是共享的,数据是各自私有一份(写时拷贝)
2.代码是逻辑,一般不可修改。数据,可读可写。进程是具有独立性的,通过数据私有,表现进程独立性。
3.父子进程fork完毕,谁先执行,不确定,这个由调度器决定。(就算在运行队列中排队也不一定,因为随时在变化)
task_struct中描述进程的属性
1.进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态,一个进程可以有几个状态
- CPU只有一个,只有一个进程正在运行,其他进程处于什么状态呢?
- 一个程序(或一段功能实现)需多个进程相互配合,一个进程实现完成,下一个进程继续实现需一个特殊进程实现完成后,才能运行,那他们分别处于什么状态
Linux中进程的状态会以数组的形式保留下来
static const char* const tast_state_array[]={
"R(running)", /*0*/
"S(sleeping)", /*1*/
"D(disk sleep)", /*2*/
"T(stoping)", /*4*/
"t(tracing stop)", /*8*/
"X(deed)", /*16*/
"Z(zombie)", /*32*/
};
R 运行状态(running)
并不意味着进程一定在运行中,它表明进程要么在运行中,要么在运行队列里
运行状态不好查看,当我们一个进程没有输入输出,不需要I/O,将其设置为死循环,即可查看其在R状态
S 睡眠状态(sleeping)
意味着进程在等待事件完成(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))
当事件就绪时,该S状态可被直接唤醒
可有多个进程都在等待,所以可存在等待队列
因为CPU的效率很快,我们去查看进程运行状态时多半都运行完了,此时,正在适配外设,打印到显示器。所以这时CPU处于睡眠状态,等待打印到显示器
注:
S+表示该进程此时正在前台运行(可用ctrl+c结束)
./文件名 &表示将其放在后台打印,此时状态为S。不可ctrl+c中止,只能用**kill -9 PID去结束进程
D 磁盘休眠状态(disk sleep)
有时候叫做不可中断睡眠状态(uninterruptible sleep),这个状态进程通常会等待IP的结束
通常在磁盘中使用
所以读取数据需要花时间,就会处于休眠等待状态
当我们OS内存紧张进程内存管理时,可能会杀掉这个IO进程,导致IO失败,这个时候我们就会把该进程设为D状态,保证进程无法被杀掉
所以S浅睡眠,可以被杀掉的,可被操作系统杀掉的
D是深睡眠,任何人都无法杀掉,除非重启或进程运行结束
T 停止状态 (stopped)
可以通过发送SIGSTOP信号来给进程来停止**(T)进程。这个被暂停的进程可以通过发送SIGCONT**信号来让进程继续
注:
kill -l可查看kill中的选项
kill -19 PID 可停止一个进程,这个时候该进程状态变为T
X 死亡状态(deed)
这个状态只是一个返回状态,你不会在任务列表里看到这个状态
Z 僵尸进程(zombie)
- **僵尸状态(zombies)是一个比较特殊的状态,当进程退出并且父进程(使用wait()**系统调用),没有读取到子进程退出的返回代码时就会产生僵尸进程
- 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态码
- 所以只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入"Z"状态
为什么会有僵尸进程?有没有什么特征?
在子进程退出时,父进程通过系统调用,OS检测进程运行完的时候,结果情况
- 是否正常运行完
- 是否出现异常
- 发生了什么异常
也就是,僵尸进程的产生是保持进程退出信息,方便父进程读取获得子进程退出原因
一般,一个进程僵尸的时候,task_struct是会被保留的,进程的退出信息,是放在进程控制块中的!
也就是说,它不跑了,结束进程了,但它不会被马上free,它的信息会得到保留,这就是僵尸进程
当它被读取后,父进程知道其退出原因,就会进入死亡状态,被free掉
注:在Linux中,每一个命令都会以进程的形式去执行echo $ ?
$?最近一个命令执行的退出码,一般执行成功的退出码为0
总:
- 僵尸进程是指先于父进程退出的子进程程序已经不再运行,但是因为保存退出原因,因此资源没有完全释放的进程,因此不会自动退出释放资源,也不会被kill命令再次杀死
- 僵尸进程必须调用waitpid、wait接口进行等待
孤儿进程
- 父进程如果提前退出,那么子进程后退出,称为孤儿进程
- 孤儿进程被1号systemed进程领养,当然要有systemed进程回收
注:操作系统!=进程,操作系统是一套管理方式,只不过是1号进程帮助我们管理