计算机操作系统

目录

一.进程的描述与控制

1.Linux之进程管理

第一关  获取进程常见属性

第二关 进程创建操作-fork

第三关 进程创建操作-vfork

第四关 进程终止

2.Linux之线程同步

第一关 互斥锁


一.进程的描述与控制

1.Linux之进程管理

第一关  获取进程常见属性

Linux 环境下,进程是一个十分重要的概念。每个进程都由一个唯一的标识符来表示,即进程ID,通常称为pid。本关将介绍如何获取进程的pid

相关知识

Linux系统中存在一个特殊的进程,即空闲进程(idle process),当没有其他进程在运行时,内核所运行的进程就是空闲进程,它的pid0。在启动后,内核运行的第一个进程称为init进程,它的pid1。通常,Linux系统中init进程就是我们在资源管理器中看到的名为init的程序。系统中其它的进程都是由init来创建出来的。

创建新进程的那个进程被称为父进程,而新创建的进程被称为子进程。每个进程都是由其他进程创建的(除了init进程),因此每个子进程都有一个父进程。

Linux系统提供了两个系统调用函数来获取一个进程的pid和其父进程的pid,分别是getpidgetppid函数。在Linux系统中可以使用man命令来查询这些函数的使用方法。具体的查询命令为: man 2 函数名

获取进程自身pid

获取进程本身的进程ID的系统调用函数是getpid,具体的说明如下:

  • 需要的头文件如下:

     
      
    1. #include <sys/types.h>
    2. #include <unistd.h>
  • 函数格式如下: pid_t getpid(void);

  • 函数返回值说明: 返回当前进程的pid值。

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全getProcInfo函数,用于获取当前进程ID和其父进程ID(提示:将结果存放在procIDInfo结构体中)。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

/**********************
 * pid: 当前进程ID
 * ppid: 父进程ID
***********************/
struct procIDInfo
{
	pid_t pid;
	pid_t ppid;
};

/************************
 * 返回值: 需要被打开的目录路径
*************************/
struct procIDInfo getProcInfo()
{
	struct procIDInfo ret;   //存放进程ID信息,并返回
	/********** BEGIN **********/
	ret.pid = getpid();
    ret.ppid = getppid();
	
	/********** END **********/

	return ret;
}

第二关 进程创建操作-fork

在上一关我们学习如何获取进程的pid信息,本关我们将介绍如何编程创建一个新的进程。

相关知识

Linux系统中创建进程有很多函数可以使用,其中包括了系统调用也包括库函数。本关将介绍一个最常见的系统调用函数来创建进程,这就是使用fork函数来创建一个新进程。

当用户调用fork函数时,系统将会创建一个与当前进程相同的新进程。通常将原始进程称为父进程,而把新生成的进程称为子进程。子进程是父进程的一个拷贝,子进程获得同父进程相同的数据,但是同父进程使用不同的数据段和堆栈段。

在早期的系统中,创建进程比较简单。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容也复制到子进程的地址空间中。但是从内核角度来说,这种复制方式是非常耗时的。

因此,在现代的系统中采取了更多的优化。现代的Linux系统采用了写时复制技术(Copy on Write),而不是一创建子进程就将所有的数据都复制一份。

Copy on Write(COW)的主要思路是:如果子进程/父进程只是读取数据,而不是对数据进行修改,那么复制所有的数据是不必要的。因此,子进程/父进程只要保存一个指向该数据的指针就可以了。当子进程/父进程要去修改数据时,那么再复制该部分数据即可。这样也不会影响到子父进程的执行。因此,在执行fork时,子进程首先只复制一个页表项,当子进程/父进程有写操作时,才会对所有的数据块进行复制操作

[COW思路]

 在Linux系统中可以使用man命令来查询该函数的使用方法。具体的查询命令为: man 2 函数名

使用fork函数创建进程

fork函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <unistd.h>
  • 函数格式如下: pid_t fork(void);

  • 函数返回值说明: 调用成功,fork函数两个值,分别是0和子进程ID号。当调用失败时,返回-1,并设置错误编号errno

注意:fork函数调用将执行两次返回,它将从父进程和子进程中分别返回。从父进程返回时的返回值为子进程的 PID,,而从子进程返回时的返回值为0,并且返回都将执行fork之后的语句。

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全createProcess函数,使用fork函数创建进程,并在子进程中输出"Children"字符串,在父进程中输出"Parent"字符串。(注意:不要在createProcess函数中使用exit函数或者return来退出程序)。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/************************
 * 提示: 不要在子进程或父进程中使用exit函数或者return来退出程序
*************************/
void createProcess()
{
	/********** BEGIN **********/
	pid_t pid;
    pid = fork();
    if(pid == 0)
        printf("Children");
    else
        printf("Parent");
	
	/********** END **********/
}

第三关 进程创建操作-vfork

在上一关我们学习使用fork函数创建新进程,本关我们将介绍如何另一种创建新进程的系统调用函数。

相关知识

在上一关卡中,我们介绍了fork的使用方法。使用fork创建的子进程的特点是:(1)子进程采用写时复制(COW)技术来为子进程创建地址空间;(2)子进程和父进程的执行顺序是由操作系统调度器来决定的。

本关将介绍Linux系统中另一个创建进程的系统调用函数vforkvfork函数是一个历史遗留产物。vfork创建进程与fork创建的进程主要有一下几点区别:

  1. vfork创建的子进程与父进程共享所有的地址空间,而fork创建的子进程是采用COW技术为子进程创建地址空间;
  2. vfork会使得父进程被挂起,直到子进程正确退出后父进程才会被继续执行,而fork创建的子进程与父进程的执行顺序是由操作系统调度来决定。

vfork性能要比fork高,主要原因是vfork没有进行所有数据的复制,尽管fork采用了COW技术优化性能,但是也会为子进程的页表项进行复制,因此vfork要比fork快。

使用vfork时要注意,在子进程中对共享变量的修改也会影响到父进程,因此vfork在带来高性能的同时,也使得整个程序容易出错,因此,开发人员在使用vfork创建进程时,一定要注意对共享数据的修改。

由于vfork创建的子进程和父进程共享所有的数据(栈、堆等等),因此,采用vfork创建的子进程必须使用exit或者exec函数族(下一关将介绍这些函数的功能)来正常退出,不能使用return来退出。

exit函数是用来结束正在运行的整个程序,exit是系统调用级别,它表示一个进程的结束;而return 是语言级别的,它表示调用堆栈的返回。

vfork函数是系统调用函数,man 2 vfork来查看其使用方法。而exit函数是库函数,因此使用man 3 exit来查看其使用方法。

使用vfork函数创建进程

vfork函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <sys/types.h>
    2. #include <unistd.h>
  • 函数格式如下: pid_t vfork(void);

  • 函数返回值说明: 调用成功,vfork函数两个值,分别是0和子进程ID号。当调用失败时,返回-1,并设置错误编号errno

注意:vfork函数调用将执行两次返回,它将从父进程和子进程中分别返回。从父进程返回时的返回值为子进程的 PID,,而从子进程返回时的返回值为0,并且返回都将执行vfork之后的语句。vfork创建的子进程必须调用exit函数来退出子进程。

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全createProcess函数,使用vfork函数创建进程,并在子进程中输出"Children"字符串(提示:需要换行),在父进程中输出"Parent"字符串(提示:需要换行)。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

/************************
 * 提示: 不要在子进程中使用return来退出程序
*************************/
void createProcess()
{
	/********** BEGIN **********/
	pid_t pid;
    pid = vfork();
    if(pid == 0)
        printf("Children\n");
    else
        printf("Parent\n");
    
	
	/********** END **********/

	exit(0);
}

第四关 进程终止

在上一关我们学习使用vfork函数创建新进程,并且使用exit来结束子进程,本关我们将介绍Linux系统中结束进程的其它方法。

相关知识

在上一关以及看到,开发人员使用vfork创建出来的子进程可以用exit函数来结束。在 Linux 环境中,一个进程的结束,可以通过调用相应的函数实现,也可以是接收到某个信号而结束。

常见与退出进程相关的函数有:exit_exitatexiton_exitabortassert

  1. exit函数是标准C库中提供的函数,它用来终止正在运行的程序,并且关闭所有I/O标准流。
  2. _exit函数也可用于结束一个进程,与exit函数不同的是,_exit不会关闭所有I/O标准流。
  3. atexit 函数用于注册一个不带参数也没有返回值的函数以供程序正常退出时被调用。
  4. on_exit 函数的作用与atxeit函数十分类似,不同的是它注册的函数具有参数,退出状态和参数arg都是传递给该程序使用的。
  5. abort 函数其实是用来发送一个SIGABRT信号,这个信号将使当前进程终止。
  6. assert是一个宏。调用assert时,它将先计算参数表达式 expression的值,如果为0,则调用abort函数结束进程。

[exit_exit区别]

以上关于退出处理函数中只有_exit是系统调用函数,因此使用man 2 _exit来查看其使用方法,而其余函数都是库函数,因此使用man 3 函数名来查看其使用方法。

 

exit_exit使用方法

exit函数的具体的说明如下:

  • 需要的头文件如下:
    1. #include <stdlib.h>
  • 函数族格式如下:
    1. void exit(int status);
    参数说明: status:设置程序退出码;

_exit函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <unistd.h>
  • 函数族格式如下:

    1. void _exit(int status);

    参数说明: status:设置程序退出码;

  • 函数返回值说明: exit_exit均无返回值。

atexiton_exit使用方法

atexiton_exit函数的具体的说明如下:

  • 需要的头文件如下:

    1. #include <stdlib.h>
  • 函数族格式如下:

    1. int atexit(void (*function)(void));
    2. int on_exit(void (*function)(int , void *), void *arg);

    参数说明: atexit函数的function参数是一个函数指针,指向无返回值和无参数的函数; on_exit函数的function参数是一个函数指针,指向无返回值和有两个参数的函数,其中第一个参数是调用exit()或从main中返回时的值,参数arg指针会传给参数function函数;

  • 函数返回值说明: atexiton_exit调用成功返回0;调用失败返回一个非零值。

注意:atexiton_exit只有在程序使用exit或者main中正常退出时才会有效。如果程序使用_exitabortassert退出程序时,则不会执行被注册的函数。

abortassert使用方法

abort函数的具体的说明如下:

  • 需要的头文件如下:
    1. #include <stdlib.h>
  • 函数族格式如下:
    1. void abort(void);

assert宏的具体的说明如下:

  • 需要的头文件如下:
    1. #include <assert.h>
  • 函数族格式如下:
    1. void assert(scalar expression);
    参数说明: expression:需要被判断的表达式;

注意:assert宏通常用于调试程序。

  • 函数返回值说明: abortassert无返回值。

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全exitProcess函数,使用atexit函数注册一个函数,在注册函数中打印出当前进程的ID号。
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>

/************************
 * 提示: 用户需要在exitProcess函数中使用atexit函数注册一个自定义函数,并在自定义函数中打印出当前进程ID号
*************************/
void ppid(){
        printf("%d",getpid());
    }
void exitProcess()
{
	/********** BEGIN **********/
	
    if(atexit(ppid) != 0)
        printf("调用atexit函数错误");
	
    return 0;
	/********** END **********/
}

2.Linux之线程同步

第一关 互斥锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值