fork 进程复制 父子进程共享文件偏移量 exec替换进程

printf函数以及缓冲区

在这里插入图片描述
先打印hello 在睡眠3s钟

去掉 \n 之后 先睡眠3s在打印hello
在这里插入图片描述
正常认知:代码是顺序执行 printf执行完后才能执行sleep. 由于缓冲区的作用,退出程序才把缓冲去的内容显示到了屏幕上.

打印的内容是如何显示到屏幕上的

当有以下三种情况时,缓冲区才会把数据传输到标准输出设备(显示器)中进行输出
1.缓冲区满
2.程序结束时
3.强制刷新缓冲区 “\n” fflush(stdout)可以达到此效果
缓冲区的内容提交给内核 利用write()打印到屏幕上

fork()复制进程

进程:一个正在运行的程序

内核把进程存放在叫做任务队列(task list)的双向循环链表中。链表中的每一项都是类型为task_struct、称为进程描述符(process descriptor)的结构,该结构定于在<linux/sched.h>文件中。进程描述符中包含一个具体进程的所有信息。

fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。
在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。

一个理解fork()小例子

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main() {
	int n = 0;
    char* s = NULL;
	pid_t id = fork();//复制进程
	assert(id != -1);
	if (id == 0) {//子进程
		n = 3;
		s = "child";
	}
	else {//父进程
		n = 7;
		s = "parent";
	}
	//子进程和父进程打印次数分别为3、7
	for (int i = 0; i < n; ++i) {
		printf("s=%s,pid=%d\n",s,getpid());
		sleep(1);
	}
	exit(0);
}

在这里插入图片描述
在这里插入图片描述

父进程先于子进程结束
修改 n 的次数也就相当于修改了子进程以及父进程执行for()的次数
在这里插入图片描述
在这里插入图片描述

fork笔试题
1.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
 int main(){
   fork()||fork();
   printf("A\n");
   exit(0); 
  }                                                                                                                                                                                                                                                

在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
 int main(){
   fork()&&fork();
   printf("A\n");
   exit(0); 
  }        

在这里插入图片描述
在这里插入图片描述

 #include<stdio.h>
 #include<unistd.h>
 #include<stdlib.h>
 int main(){    
 for(int i=0;i<2;++i){
      fork();
      printf("A\n");
  }
    exit(0); 
}   

在这里插入图片描述
在这里插入图片描述

4.去掉3中的\n

 #include<stdio.h>
 #include<unistd.h>
 #include<stdlib.h>
 int main(){    
 for(int i=0;i<2;++i){
      fork();
      printf("A");
  }
    exit(0); 
}   

在这里插入图片描述
在这里插入图片描述

父进程先终止
系统保证每个进程都有一个父进程,若父进程比子进程先终止,则该父进程的所有子进程的父进程都改变为init进程。我们称这些进程由init进程领养。其执行顺序大致如下:在一个进程终止时,内核逐个检查所有活动的进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID)。

僵死进程及处理方法

(1) 僵死进程概念:子进程先于父进程结束,父进程没有调用 wait 获取子进程退出码。
(2)僵死进程的危害:如果父进程不调用wait()/waitpid()的话,那么存在于那个位置的信息就不会被释放,它的PCB就会被永远占用,但是系统的进程表的容量是有限的,所能使用的进程号也是有限的,所以如果产生了大量的僵死进程,将因为没有可用的进程而导致系统不能产生新的进程,也就无法fork();所以,僵死进程不仅占用系统的内存信息,还影响了系统的性能,如果大量产生,还会导致系统瘫痪.
(3) 如何处理僵死进程:父进程通过调用 wait()完成。
(4) Init 进程收养孤儿进程

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main() {
	int n = 0;
    char* s = NULL;
	pid_t id = fork();//复制进程
	assert(id != -1);
	if (id == 0) {//子进程
		n = 3;
		s = "child";
	}
	else {//父进程
		n = 7;
		s = "parent";
	}
	//子进程和父进程打印次数分别为3、7
	for (int i = 0; i < n; ++i) {
		printf("s=%s,pid=%d\n",s,getpid());
		sleep(1);
	}
	exit(0);
}

在这里插入图片描述

父进程通过调用 wait()获取子进程的退出码解决了僵死进程

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/wait.h>
int main() {
	int n = 0;
    char* s = NULL;
	pid_t id = fork();//复制进程
	assert(id != -1);
	if (id == 0) {//子进程
		n = 3;
		s = "child";
	}
	else {//父进程
		n = 7;
		s = "parent";
		int val=0;
		wait(&val);
		printf("val=%d\n",val);
	}
	//子进程和父进程打印次数分别为3、7
	for (int i = 0; i < n; ++i) {
		printf("s=%s,pid=%d\n",s,getpid());
		sleep(1);
	}
	exit(0);
}

pid_t wait(int *status); *status 是子进程的结束状态值(exit(0)中的 0 在系统中会通过左移8位显示出),返回值为子进程PID.
如果子进程没结束那么wait会阻塞
在这里插入图片描述
在这里插入图片描述
从上图可以看到子进程结束后,彻底从系统中消失,并没有变成僵死进程.

*int wait(int status) 函数的宏

1.WIFEXITED(status)这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。(此处status是个整数).
2.WEXITSTATUS(status)当WITEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(3)退出,WEXITSTATUS(status)就会返回3;如果子进程调用exit(4)退出,WEXITSTATUS(status)就会返回4.如果进程不是正常退出,也就是说,WIFEXITED返回0,这个值就毫无意义.

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/wait.h>
int main() {
	int n = 0;
    char* s = NULL;
	pid_t id = fork();//复制进程
	assert(id != -1);
	if (id == 0) {//子进程
		n = 3;
		s = "child";
	}
	else {//父进程
		n = 7;
		s = "parent";
		int val=0;
		int id=wait(&val);
		if(WIFEXITED(val)){
		printf("id=%d,val=%d\n",id,WEXITSTATUS(val));
		}		
	}
	//子进程和父进程打印次数分别为3、7
	for (int i = 0; i < n; ++i) {
		printf("s=%s,pid=%d\n",s,getpid());
		sleep(1);
	}
	exit(0);
}

在这里插入图片描述

操作文件的系统调用:open,read,write,close 返回值为文件描述符(int)

open read write close 系统调用,实现在内核中

PCB(进程描述符\进程控制块)用struct task_struct;实现.文件描述符表是此结构体的成员
在这里插入图片描述

每次打开文件(open操作时 ),会产生struct file这样一个结构体在操作系统内核中,用来表示打开的这个文件,记录着文件偏移量(起始从0开始,随着写入数据进行增加),引用计数(使用此文件的进程个数),inode节点(存放进程的属性信息:创建者,存储信息。通过inode节点,能找到对应的文件)

1.先打开文件在复制进程(先open在fork)

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

int main(){

     int fd=open("file1.txt",O_RDONLY);//abcdef
     assert(fd!=-1);

     pid_t pid=fork();
     assert(pid!=-1);

     if(pid==0){//子进程
    //每次读一个字符并输出
     char buff[8]={0};
     read(fd,buff,1);
     printf("child buff=%s\n",buff);
     sleep(1);
     read(fd,buff,1);
     printf("child buff=%s\n",buff);
     }
    else{//父进程
      //每次读一个字符并输出
       char buff[8]={0};
       read(fd,buff,1);
       printf("parent buff=%s\n",buff);
       sleep(1);
       read(fd,buff,1);
       printf("parent buff=%s\n",buff);
    }
    close(fd);
    exit(0);
 }

运行结果在这里插入图片描述

结论:子进程可以使用fork之前open返回的文件描述符。调用fork之后,只拷贝了PCB本身,拷贝的只是指针,没有拷贝指针所指向的内容,这种情况叫做浅拷贝。子进程的指针依旧指向struct file,所以父子进程对于文件描述符和文件偏移量是共享的。

在这里插入图片描述
此时父子进程共享此文件的属性,引用计数==2,父子进程都执行close()才可以彻底关闭文件.

2.先复制进程在打开文件(先fork在open)

在这里插入图片描述
在这里插入图片描述
父子进程各自打开各自的文件
示意图如下:在这里插入图片描述

替换进程

进程的产生:fork+exec
exec函数作用:把当前进程替换为一个新进程,且新进程与原进程有相同的PID.并从新程序的main第一行开始执行.替换成功,原程序就会被替换为新程序,并执行新程序.
exec系列

int execl(const char *pathname, const char arg, … / (char *) NULL */);
int execlp(const char *file, const char arg, … / (char *) NULL */);
int execle(const char *pathname, const char arg, …/, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
函数返回值: 成功不返回,失败返回-1.

分为两大类:execl()系列和execv()系列
path参数表示你要启动程序的名称包括路径名
l:list要求将新进程的每个命令行参数都说名为一个单独的参数,以空指针结尾.
v: 是指把所有参数放到容器(数组, vector)中再一次性传入.
p: 是指第1个参数位于默认的环境变量PATH中,忽略pathname,仅用文件(file)指出文件名即可.
e:是指第1个参数位于给定的envp环境变量中., 用绝对路径(path)给出待执行文件.

使用ps替换main

execl()
在这里插入图片描述
在这里插入图片描述
替换之后进程name是ps,替换后的进程的pid和main的pid相同,这说明替换进程并不改变原有pid.

其它execl系列的使用(只列出了较上面程序不同的部分)

*****可以不用给全路径只给命令名字 
execlp("ps","ps","-f",(char*)NULL);
//有e需要环境变量
execle("/usr/bin/ps","ps","-f",(char*)NULL,envp);

execv系列
在这里插入图片描述
在这里插入图片描述

其它execv系列的使用(只列出了较上面程序不同的部分)

***可以不用给全路径只给命令名字 
execvp("ps",myargv);
***有p可以只给命令名字
execvpe("ps",myargv,envp);
//有e需要环境变量
execve("/usr/bin/ps",myargv,envp);
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值