【学习点滴】linux笔记,fork底层实现、信号、进程、vi

本文详细介绍了Linux系统中的进程概念,包括Vi编辑器的三种模式、fork函数的底层实现、进程间通信以及信号的发送与捕捉。特别强调了fork函数如何复制父进程的资源,并探讨了孤儿进程和僵尸进程的状态。此外,文章还提到了wait函数的使用以及exec函数族在子进程中的作用。最后,讨论了命名管道和实时信号在进程间通信中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

Vi三种模式详解

进程

父子进程究竟共享啥,复制了啥:

1.fork()函数

fork底层实现

wait:

exec函数族:可让子进程做其他事

进程间通信:

信号:

      信号的发送

kill()

  sigqueue()

  alarm()

  setitimer()

 abort()

 raise()

信号的捕捉

 signal()

sigaction()

 信号集及信号集操作函数:

信号阻塞与信号未决:


linux下安装gcc和g++

su 到root用户下,输入命令

yum install gcc 即可安装gcc,然后再

yum install gcc-c++ 即可安装g++

 

第一次在linux下编译运行c++程序:

用vim编辑器新建一个cpp文件:vim helloworld.cpp

此文件中写好c++程序,然后:wq退出

在终端中输入  g++ -o main helloworld.cpp -lm 即可编译cpp程序,并将生成的程序命名为main

若编译成功,则不会返回任何信息

然后输入./main来调用刚刚生成的文件,效果:

 

 

Vi三种模式详解

命令行模式 (command mode/一般模式)
  任何时候,不管用户处于何种模式,只要按一下“ESC”键,即可使Vi进入命令行模式;我们在shell环境(提示符为$)下输入启动Vi命令,进入编辑器时,也是处于该模式下。 
  在该模式下,用户可以输入各种合法的Vi命令,用于管理自己的文档。此时从键盘上输入的任何字符都被当做编辑命令来解释,若输入的字符是合法的Vi命令,则Vi在接受用户命令之后完成相应的动作。但需注意的是,所输入的命令并不在屏幕上显示出来。若输入的字符不是Vi的合法命令,Vi会响铃报警。
 
文本输入模式 (input mode/编辑模式)
  在命令模式下输入插入命令i(insert)、附加命令a (append)、打开命令o(open)、修改命令c(change)、取代命令r或替换命令s都可以进入文本输入模式。在该模式下,用户输入的任何字符都被Vi当做文件内容保存起来,并将其显示在屏幕上。在文本输入过程中,若想回到命令模式下,按"ESC"键即可。 

末行模式 (last line mode/指令列命令模式)
  末行模式也称ex转义模式。 
  Vi和Ex编辑器的功能是相同的,二者主要区别是用户界面。在Vi中,命令通常是单个键,例如i、a、o等;而在Ex中,命令是以按回车键结束的正文行。Vi有一个专门的“转义”命令,可访问很多面向行的Ex命令。在命令模式下,用户按“:”键即可进入末行模式下,此时Vi会在显示窗口的最后一行(通常也是屏幕的最后一行)显示一个“:”作为末行模式的提示符,等待用户输入命令。多数文件管理命令都是在此模式下执行的(如把编辑缓冲区的内容写到文件中等)。末行命令执行完后,Vi自动回到命令模式。

 

 

dd命令用于复制文件并对原文件的内容进行转换和格式化处理。dd命令功能很强大的,对于一些比较底层的问题,使用dd命令往往可以得到出人意料的效果。用的比较多的还是用dd来备份裸设备。但是不推荐,如果需要备份oracle裸设备,可以使用rman备份,或使用第三方软件备份,使用dd的话,管理起来不太方便。 

 

df命令用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为KB。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。 
 

top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。通过top命令所提供的互动式界面,用热键可以管理。 

netstat命令用来打印Linux中网络系统的状态信息,可让你得知整个Linux系统的网络情况。 

 

du查看目录大小,df查看磁盘使用情况。
我常使用的命令(必要时,sudo使用root权限),
1).查看某个目录的大小:du -hs /home/master/documents
查看目录下所有目录的大小并按大小降序排列:sudo du -sm /etc/* | sort -nr | less
2).查看磁盘使用情况(文件系统的使用情况):sudo df -h
df --block-size=GB
-h是使输出结果更易于人类阅读;du -s只展示目录的使用总量(不分别展示各个子目录情况),-m是以 
MB为单位展示目录的大小(当然-k/-g就是KB/GB了)。

3,du使用详细案例
a:显示全部目录和其次目录下的每个档案所占的磁盘空间
s:只显示各档案大小的总合 
b:大小用bytes来表示
x:跳过在不同文件系统上的目录不予统计
a:递归地显示指定目录中各文件及子孙目录中各文件占用的数据块数
...

 

 

进程

 

父子进程究竟共享啥,复制了啥:

#include <stdio.h>
#include <stdlib.h>
#include < unistd.h>
int global = 1; //初始化的全局变量,存在data段
int main(){
	pid_t pid;
	int stack =1; //局部变量,存在栈
	int *heap;	//指向堆变量的指针
	heap=(int *)malloc(sizeof(int));
	*heap=3;
	pid=fork();
	if(pid<0){
		perror("fail to fork");
		exit(-1);
	}
	else if(pid==0){
		//子进程, 改变变量的值
		global++;
		stack++;
		(*heap)++;
		printf("in sub-process, global:%d, stack:%d, *heap:%d \n", global,stack,*heap);
		exit(0);
	}
	else{
		sleep(2);	//休眠2 秒钟,确保子进程已执行完毕, 再执行父进程
		printf("in parent-process, global:%d, stack:%d, *heap:%d \n", global,stack,*heap);
	}
	
	return 0;
}

执行结果:

事实上,子进程完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。但是,子进程并没有复制代码段,而是和父进程共用代码段。这样做是合理的,因为子进程可能执行不同的流程来改变数据段和堆栈段,因此需要分开存储父子进程各自的数据段和堆栈段。但是代码段是只读的,不存在被修改的问题,因此代码段可以让父子进程共享,以节省存储空间。父进程和子进程共享fork之后的程序段、打开的文件、工作目录、共享内存,注意锁不继承。

 

 

1.fork()函数

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
    pid_t pid;
    printf("xxxxxxxxxxxxxxx\n");
    
    pid = fork();
    if(pid == -1){
        perror("fork error:");
        exit(1);
    }
    else if(pid==0)
        printf("i'm child ,pid = %u,ppid=%u \n",get_pid(),get_ppid());
    else{
        printf("i'm parent,pid = %u,ppid=%u \n",get_pid(),get_ppid());
        sleep(1);
    }

    printf("yyyyyyyyyyyyyyy\n");
    return 0;

}

循环创建n个子进程:


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
    int i;
    pid_t pid;
    printf("xxxxxxxxxxxxxxx\n");
    
    for(i=0;i<5;i++){
    pid = fork();
    if(pid == 0){
        break;
    }
    if(i<5)
        printf("i'm child ,pid = %u,ppid=%u \n",get_pid(),get_ppid());
    else{
        printf("i'm parent,pid = %u,ppid=%u \n",get_pid(),get_ppid());
        sleep(i);
    }

    printf("yyyyyyyyyyyyyyy\n");
    return 0;

}
 

2.fork()、vfork()、clone()的区别

进程的四要素:

(1)有一段程序供其执行(不一定是一个进程所专有的),就像一场戏必须有自己的剧本。

(2)有自己的专用系统堆栈空间(私有财产)

(3)有进程控制块(task_struct)(“有身份证,PID”)

(4)有独立的存储空间。

缺少第四条的称为线程,如果完全没有用户空间称为内核线程,共享用户空间的称为用户线程。

一、fork()

fork()函数调用成功:返回两个值; 父进程:返回子进程的PID;子进程:返回0;

失败:返回-1;

 

fork 创造的子进程复制了父亲进程的资源(写时复制技术),包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。

读时共享写时复制(Copy-On-Write):在fork()之后exec之前两个进程用的是相同的物理空间(内存区),先把页表映射关系建立起来,并不真正将内存拷贝。子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父进程中有更改相应段的行为发生时,如进程写访问,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。fork时子进程获得父进程数据空间、堆和栈的复制所以变量的地址(当然是虚拟地址)是一样的。

二、vfork()

vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。

3.clone

clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。

clone函数功能强大,带了众多参数,它提供了一个非常灵活自由的常见进程的方法。因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。

问题:clone和fork的区别:

(1) clone和fork的调用方式很不相同,clone调用需要传入一个函数,该函数在子进程中执行。

(2)clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。

 

fork底层实现

linux通过clone()系统调用实现fork()。

fork(),vfork(),和_clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork()

do_fork()完成了创建中的大部分工作,它定义在kernel/fork.c中。该函数调用copy_process(),copy_process()实现的工作如下

  • 1.调用dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。
  • 2.检查新创建的这个子进程后,当前用户所拥有的进程数目没有超出给他分配的资源的限制。
  • 3.现在,子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。进程描述符的成员值并不是继承而来的,而主要是统计信息,进程描述符中大多数的数据都是共享的。
  • 4.接下来,子进程的状态被设置为TASK_UNINTERRUPTIBLE(不可中断)以保证它不会投入运行。
  • 5.copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0.表名进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置。
  • 6.调用get_pid()为新进程获取一个有效的PID
  • 7.根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件,文件系统信息,信号处理函数,进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里。
  • 8.让父进程和子进程平分剩余的时间片
  • 9.最后copy_process()做扫尾工作并返回一个指向子进程的指针

再回到do_fork()函数,如果copy_process函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。因为一般子进程都会马上调用exec()函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值