Linux进程操作(进程控制--进程操作)

进程操作

创建一个进程

进程是系统中基本的执行单位。Linux环境下使用 fork 函数创建一个新进程,函数原型为:

#include<unistd.h>
pid_t fork(void);
  • Linux系统中任何一个进程都是由其他进程创建的,创建新进程的进程,即调用fork函数的进程就是父进程。
  • 返回值:
    对于父进程,fork 函数返回新创建的子进程的ID;
    对于子进程,fork 函数返回0。由于系统的0号进程是内核进程,所以子进程的进程号不可能是0。由此区别父进程和子进程;
    如果出错,fork 函数返回-1。
  • 特点: fork函数会创建一个新的进程,并从内核中为此进程得到一个新的可用进程ID.之后为这个新进程分配进程空间,并将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时候,系统中又多了一个进程,这个进程和父进程一模一样,两个进程都要接受系统的调度。 由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork 函数中,等待返回。因此,fork 函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
  • 案例: 创建一个新进程,并打印相关信息
#include<unistd.h>

int main(){

	pid_t pid;
	pid=fork();
	
	if(pid<0){
		perror("fail to fork\n");
		exit(1);
	}else if(0 == pid){
		printf("this is chiuld,pid is %u\n",getpid());
	}else{
		printf("this is parent,pid is %u,child pid is %d\n",getpid(),pid);
	}
	return 0;
}

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

  • 说明:由于创建的新进程和父进程在系统看来是地位平等的两个进程,所以运行机会是一样的,读者不能对其执行顺序进行假设,先执行哪一个进程取决于系统的调度算法,如果想确保运行顺序,需要额外的操作。

父子进程的共享资源

  • 子进程和父进程:子进程完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。子进程并没有复制代码段,而是和父进程共用代码段。这样做是合理的,由于子进程可能执行不同的流程,因此会改变数据段和堆栈。但是代码段是只读的,不存在修改的问题,因此父子进程可以公用这些代码段
  • 子进程继承的资源情况:
    在这里插入图片描述
  • 写时复制:现在的Linux内核实现fork函数时往往实现为子进程先于父进程共享代码段数据段 和堆栈段,当子进程修改这些共享内容时,复制才会发生,内核才会给子进程分配进程空间将父进程的内容复制过来,继续后面的操作。这样的实现更加合理,对于一些只是为复制自身完成一些工作的进程来说,这样做的效率会更高。这就是现代操作系统中一个重要的概念一“写时复制”的一个重要体现。

fork函数的出错情况

fork函数出错会返回-1,有两种情况会导致出错:

  • 系统中已经有太多的进程存在;
  • 调用fork函数的用户的进程太多了。

创建一个共享空间的子进程

进程在创建一个新的子进程之后,子进程的地址空间完全和父进程分开。父子进程是两个独立的进程,接受系统调度和分配系统资源的机会均等。因此父进程和子进程更像是一对兄弟。如果父子进程公用父进程的地址空间,则子进程就不是独立于父进程的

  • Linux使用vfork函数创建一个公用父进程地址空间的子进程,函数原型为:
#include<unistd.h>

pid_t vfork();
  • vfork 和fork函数的区别有:

(1)vfork函数产生的子进程和父进程完全共享地址空间,包括代码段、数据段和堆栈段,子进程对这些共享资源所做的修改,可以影响 到父进程。由此可知,vfork 函数与其说是产生了一个进程,不如说是产生了一个线程。
(2)vfork函数产生的子进程一定比父进程先运行,也就是说父进程调用了vfork 函数后会等待子进程运行后再运行。

  • 子进程改变的变量值在父进程中同样可以检查到!!!

在函数内部调用vfork函数

  • 警告: 不要在任何函数(非main函数)中调用 vfork 函数;
  • 案例:
#include<stdio.h>
#include<unistd.h>

int f1(){
	vfork();
	return 0;
}
int f2(int a,int b){
	return a+b;
}
int main(){
	int c;
	f1();
	c=f2(1,2);
	printf("%d\n",c);
	return 0;
}
程序运行出现段错误!!

在这里插入图片描述

  • 原因: 左边这张图说明 vfork 之后产生了一个子进程,并且和父进程共享堆栈段,两个进程都要从 f1 函数中返回。由于子进程先于父进程运行,所以子进程从f1 函数中返回,并且调用 f2 函数。其栈帧覆盖了原来 f1 函数的栈帧。当子进程结束运行,父进程运行时,就出现了右图的情景,父进程需要从 f1 函数中返回,但是 f1 函数的栈帧已经被 f2 函数所取代,因此就会出现父进程返回出错,发生段错误的情况。
  • 因此,使用 vfork 后,子进程对父进程的影响是巨大的,其同步措施势在必行,由此也可以体会到线程同步的重要性!

退出进程

当一个进程需要退出时,需要调用退出函数,这个退出函数会深入内核注销掉进程的内核数据结构,并且释放进程的资源。Linux环境下使用exit函数退出进程,其函数原型如下:

#include<stdlib.h>
void exit(int status);

:c 程序中的 return 会被编译器翻译为调用 exit 函数,例如:return 1 -》exit(1);

使用exit函数检查进程出错信息

通常 exit 函数的参数是该程序的退出状态,如果正常退出,参数为0;如果异常退出,参数为非零。用户也可以将ermo变量作为参数传递给exit函数,这样可以在程序退出后检查程序退出的原因。

exit函数与内核函数的关系

exit函数是一个标准的库函数,其内部封装了Linux系统调用__exit 函数。两者的主要区别在于exit函数会在用户空间做一些善后工作, 例如,清理用户的I/O缓冲区,将其内容写入磁盘文件等,之后才进入内核释放用户进程的地址空间:而 __exit 函数直接进入内核释放用户进程的地址空间,所有用户空间的缓冲区内容都将丢失。

设置进程所有者

  • Linux环境下使用setuid函数改变一个进程的实际用户ID和有效用户ID,函数原型为:
#include<unistd.h>
int setuid(uid_t uid);
int seteuid(uid_t uid)
int setgid(gid_t gid);
int setegid(gid_t gid);

调试多进程(gdb调试)

  • 设置跟踪流
    在进程调用了 fork 函数后,gdb 可以通过设置跟踪流选项的方式指定跟踪父进程还是子进程,其设置方式如下:
set follow-fork-mode [parent | child]

此外还可以使用detach-on-fork 参数,指示gdb在进程调用fork 函数之后是否断开(detach)某个进程的调试,或者都交由gdb控制。

set detach-on-fork [on | off]

如果选项中选择on,则断开调试fllow-fork-mode指定的进程。如果选项中选择off,gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停状态。

  • 使用gdb的attach命令
    第2种方法是使用gdb的atach命令。gdb调试器中的atch命令可以调试一个已经运行的进程,在进程调用fork函数后可以使用atch命令调试子进程。前提是要知道子进程的进程ID,并且子进程能够等待调试的开始。为了解决这两个问题,用户需要在待调试的子进程代码中添加一些辅助调试的代码。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值