栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

linux系统编程---进程总结

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

linux系统编程---进程总结

进程控制总结
  • 1 进程创建的三种方式
    • fork
    • vfrok
    • clone
  • 2 进程终止
    • 进程正常退出
      • return
      • exit
      • _exit
    • 进程异常退出
      • 进程收到某个信号,而该信号使进程终止
      • abort
  • 3 进程等待
    • 进程等待的方法
      • wait
      • waitpid
  • 4 进程替换
    • 替换原理
    • 替换函数
    • 制作一个简单的shell

1 进程创建的三种方式

参考文章:
https://zhuanlan.zhihu.com/p/498427466?utm_source=wechat_session&utm_medium=social&utm_oi=977698418977746944&utm_campaign=shareopn

https://blog.csdn.net/gogokongyin/article/details/51178257

在linux中主要提供了fork、vfork、clone三个进程创建方法。在Linux源码中,这三个调用的执行过程是执行fork()、vfork()、clone()时,通过一个系统调用表映射到sys_fork()、sys_vfork()和sys_clone(),再在这三个函数中去调用do_fork()去做具体的创建进程工作。

fork

fork创建一个进程时,复制出来的子进程有自己的task_struct结构体和pid,然后复制父进程其他所有的资源。
例如,要是父进程打开了五个文件,那么子进程也有五个打开的文件,而且这些文件的当前读写指针也停在相同的地方。

这样得到的子进程独立于父进程,具有良好的并发性。但是子进程需要复制父进程很多资源,所以fork是一个开销很大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程,其子进程仅仅是为了调用exec执行另一个可执行文件,那么fork过程对于虚拟空间的复制将是一个多余的过程。

但由于现在Linux采取了copy-on-write(写时复制)技术,fork最初不会真的产生两个不同的拷贝。写时复制是在推迟真正的数据拷贝,若后来确实发生了写入,那意味着父进程和子进程的数据不一致了,就需要产生复制动作,每个进程拿到属于自己的那一份。所以有了写时复制后,vfork其实现意义就不大了。

fork调用一次,返回两个值,对于父进程,返回的是子进程的pid值,对于子进程,返回的是0 。 在fork之后,子进程和fork都会继续执行fork调用之后的指令。

#include 
#include 
#include 
#include 

int main(void)
{
    int a=5,b=2;
    pid_t pid;
    pid = fork();
    if(pid==0)
    {
        
        a = a-4;
        printf("child process  PID = %d,a=%d,b=%dn",getpid(),a,b);
    }else if(pid >0)
    {
        
        printf("parent process PID = %d, a=%d,b=%dn",getpid(),a,b);
    }else{
        perror("fork error");
        exit(1);
    }
    return 0;
}


可见,子进程中将变量a的值该为1,而进程中则保持不变。

vfrok

vfork系统调用不同于fork,用vfork创建的子进程与父进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,如果这时子进程修改了某个变量,这将影响父进程。

因此,如果fork的例程改用vfork的话,那么两次打印a、b的值是相同的,所在地址也是相同的。

#include 
#include 
#include 
#include 

int main(void)
{
    int a=5,b=2;
    pid_t pid;
    pid = vfork();
    if(pid==0)
    {
        
        a = a-4;
        printf("child process  PID = %d,a=%d,b=%dn",getpid(),a,b);
        exit(0);
    }else if(pid >0)
    {
        
        printf("parent process PID = %d, a=%d,b=%dn",getpid(),a,b);
    }else{
        perror("fork error");
        exit(1);
    }
    return 0;
}

但此处有一点要注意的是,用vfork创建的子进程必须先调用exit()来结束,否则子进程将不能结束,fork则不存在这个情况。

vfork也是在父进程中返回子进程的进程号,在子进程中返回0,用vfork创建子进程后,父进程会被阻塞直到子进程调用exec(exec将一个新的可执行文件载入到地址空间并执行)或exit。vfork的好处是在子进程被创建后往往仅仅是为了调用exec执行另一个程序,因为它就不会对父进程的地址空间由任何引用,因此通过vfork共享内存可以减少不必要的开销。

#include 
#include 
#include 
#include 

int main(void)
{
    int a=5,b=2;
    pid_t pid;
    pid = fork();
    if(pid==0)
    {
        
        if(execl("./vfork_example","example",NULL)<0)
        {
            perror("exec error");
            exit(1);
        }
    }else if(pid >0)
    {
        
        printf("parent process  a=%d,b=%d,the address a = %p ,b=%pn",a,b,&a,&b);
    }else{
        perror("vfork error");
        exit(1);
    }
    return 0;
}

vfork_example.c

#include 
#include 
int main(void)
{
    int a=1,b=2;
    sleep(3);
    printf("child process,a=%d,b=%d,the address a =%p,b =%pn",a,b,&a,&b);
    return 0;
}


子进程调用了exec,父进程会继续执行,子进程sleep(3),所以父进程会提前结束。

clone

系统调用fork()和vfork()是无参数的,而clone()则带有参数。fork()是全部复制,vfork()是共享内存,而clone是可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制那些资源给子进程,由参数列表中的clone_flags来决定。

int clone(int (*fn)(void *), void *child_stack,
                 int flags, void *arg, ...
                  );
 fn为函数指针,此指针指向一个函数体,即想要创建进程的静态程序(我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", );
 child_stack为给子进程分配系统堆栈的指针(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值);
  arg就是传给子进程的参数一般为(0);
 flags为要复制资源的标志,描述你需要从父进程继承那些资源(是资源复制还是共享,在这里设置参数:

下面是flags可以取的值

标志含义
CLONE_PARENT创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE若父进程被trace,子进程也被trace
CLONE_VFORK父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM子进程与父进程运行于相同的内存空间
CLONE_PID子进程在创建时PID与父进程一致
CLONE_THREADLinux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int variable,fd;

int do_something(void*arg)
{
    variable = 42;
    printf("in child processn");
    close(fd);
    return 0;
}

int main(void)
{
    void *child_stack;
    char tempch;
    variable = 9;
    fd = open("./test.txt",O_RDONLY);
    child_stack=(void *)malloc(16384);
    printf("The varibale is %dn",variable);
    clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);
    sleep(3);
    printf("The variable is now %dn",variable);
    if(read(fd,&tempch,1)<1)
    {
        perror("file read error");
        exit(1);
    }
    printf("we could read from the filen");
    return 0;
}


我们在clone指定了CLONE_VM和CLONE_FILES,所以子进程与父进程共享相同的文件描述符(file descriptor)表以及子进程与父进程运行于相同的内存空间,所以会出现上述情况。

2 进程终止

参考文章:
https://zhuanlan.zhihu.com/p/435709371

https://zhuanlan.zhihu.com/p/63424197

进程正常退出 return

在main函数中使用return退出进程。return num等同于exit(num),所做的事可以看下面的exit介绍。

exit

exit函数可以在代码中任何位置使进程退出,并且exit在退出进程前还会做一系列工作:

  1. 调用用户通过atexit或on_exit定义的函数
  2. 关闭所有打开的流,所有的缓存数据均被刷新
  3. 调用_exit函数终止进程。
#include 
#include 

void show()
{
    printf("hello  world");
    exit(1);
}
int main(void)
{
    show();
    return 0;
}

终止进程前会将缓冲区当中的数据输出。

_exit

_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作。
我们将上面代码中的exit函数改成_exit函数,运行会没有输出。

进程异常退出 进程收到某个信号,而该信号使进程终止

例如,在进程运行过程中向进程发生kill -9信号使得进程异常退出,或是使用Ctrl+C使得进程异常退出等。

abort

调用abort()函数,会使进程异常终止。

3 进程等待

https://zhuanlan.zhihu.com/p/435709371

进程等待的方法 wait
pid_t wait(int* status);

等待任意子进程退出,status保存子进程的退出码。所以父进程会被阻塞,直到子进程退出。WEXITSTATUS(status)宏可以获取子进程的退出值。

on success, returns the process ID of the terminated child; on error, -1 is returned.

#include 
#include 
#include 
#include 
#include 


int main(void)
{
    pid_t pid = fork();
    if(pid==0)
    {
        int count = 10;
        while(count--)
        {
            printf("Child process : PID = %d; PPID : %dn",getpid(),getppid());
            sleep(1);
        }
      
    }
    else if(pid>0)
    {
        int status;
        pid_t ret = wait(&status);
        if(ret>0)
        {
            printf("wati child success n");
            printf("child process pid = %d,return status=%dn",ret,WEXITSTATUS(status));
        }
    }
    else{
        printf("fork errorn");
        exit(1);
    }
      exit(0);
}

waitpid

函数原型:

pid_t waitpid(pid_t pid, int *wstatus, int options);

参数含义:
pid:

     < -1   meaning wait for any child process whose process group ID is equal to the absolute value of pid.

       -1     meaning wait for any child process.

       0      meaning wait for any child process whose process group ID is equal to that of the calling process.

       > 0    meaning wait for the child whose process ID is equal to the value of pid.

options的值是下面0个或多个或(OR)值

  • WNOHANG (wait no hung): 即使没有子进程退出,它也会立即返回,直接返回0,不会像wait那样永远等下去。
  • WUNTRACED :用于调试。
    如果孩子已经停止(但没有通过 ptrace(2) 跟踪),也会返回。 即使未指定此选项,也会提供已停止的跟踪子项的状态。

state和wait一样。

#include 
#include 
#include 
#include 
#include 


int main(void)
{
    pid_t pid = fork();
    if(pid==0)
    {
        int count = 10;
        while(count--)
        {
            printf("Child process : PID = %d; PPID : %dn",getpid(),getppid());
            sleep(1);
        }
      
    }
    else if(pid>0)
    {
        int status;
        pid_t ret = waitpid(pid,&status,0);
        if(ret>0)
        {
            printf("wati child success n");
            printf("child process pid = %d,return status=%dn",ret,WEXITSTATUS(status));
        }
    }
    else{
        printf("fork errorn");
        exit(1);
    }
      exit(0);
}

运行的结果和wait一样。

4 进程替换

原文链接:
https://zhuanlan.zhihu.com/p/435709371

替换原理

用fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),如想让子进程执行另一个程序,往往需要调用一种exec函数。

当进程调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动代码开始执行。

  1. 当进程程序被替换后,有没有创建新的进程?
    进程程序被替换之后,该进程对应的PCB、进程地址空间 以及页表等数据结构都没法发生改变,只是进程在物理内存当中的数据和代码发生了改变,所有并没有创建新的进程,而且进程程序替换前后该进程的pid并没发生改变。

  2. 子进程进行进程程序替换后,会影响父进程的代码和数据吗?
    子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此子进程进行程序替换后不会影响父进程的代码和数据。

替换函数

替换函数有六种以exec开头的函数,它们统称为exec函数。

	   int execl(const char *path, const char *arg, ...
                       );
       int execlp(const char *file, const char *arg, ...
                       );
       int execle(const char *path, const char *arg, ...
                       );
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execve(const char *file, char *const argv[],
                       char *const envp[]);

exec函数的后缀含义如下:

  • l(list):表示参数采用列表的形式
  • v(vector):表示参数采用数组的形式
  • p(path):表示能自动搜素环境变量PATH,进行程序查找
  • e(env):表示可以传入自己设置的环境变量。

事实上,只有execve才是真正的系统调用,其它五个函数最终都是调用的execve,所以execve在man手册的第2节,而其它五个函数在man手册的第3节,也就是说其他五个函数实际上是对系统调用execve进行了封装,以满足不同用户的不同调用场景的。

制作一个简单的shell

shell也就是命令行解释器,其运行原理就是:当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可。

其实shell需要执行的逻辑非常简单,其只需循环执行以下步骤:

  1. 获取命令行。
  2. 解析命令行。
  3. 创建子进程。
  4. 替换子进程。
  5. 等待子进程退出。

其中,创建子进程使用fork函数,替换子进程使用exec系列函数,等待子进程使用wait或者waitpid函数。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后的最大个数
int main()
{
	char cmd[LEN]; //存储命令
	char* myargv[NUM]; //存储命令拆分后的结果
	char hostname[32]; //主机名
	char pwd[128]; //当前目录
	while (1){
		//获取命令提示信息
		struct passwd* pass = getpwuid(getuid());
		gethostname(hostname, sizeof(hostname)-1);
		getcwd(pwd, sizeof(pwd)-1);
		int len = strlen(pwd);
		char* p = pwd + len - 1;
		while (*p != '/'){
			p--;
		}
		p++;
		//打印命令提示信息
		printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);
		//读取命令
		fgets(cmd, LEN, stdin);
		cmd[strlen(cmd) - 1] = '';
		//拆分命令
		myargv[0] = strtok(cmd, " ");
		int i = 1;
		while (myargv[i] = strtok(NULL, " ")){
			i++;
		}
		pid_t id = fork(); //创建子进程执行命令
		if (id == 0){
			//child
			execvp(myargv[0], myargv); //child进行程序替换
			exit(1); //替换失败的退出码设置为1
		}
		//shell
		int status = 0;
		pid_t ret = waitpid(id, &status, 0); //shell等待child退出
		if (ret > 0){
			printf("exit code:%dn", WEXITSTATUS(status)); //打印child的退出码
		}
	}
	return 0;
}


说明:
当执行./myshell命令后,便是我们自己实现的shell在进行命令行解释,我们自己实现的shell在子进程退出后都打印了子进程的退出码,我们可以根据这一点来区分我们当前使用的是Linux操作系统的shell还是我们自己实现的shell

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/825921.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号