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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>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=%d\n",getpid(),a,b);}else if(pid >0){/* 这是父进程 */printf("parent process PID = %d, a=%d,b=%d\n",getpid(),a,b);}else{perror("fork error");exit(1);}return 0;
}

在这里插入图片描述
可见,子进程中将变量a的值该为1,而进程中则保持不变。

vfrok

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

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

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>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=%d\n",getpid(),a,b);exit(0);}else if(pid >0){/* 这是父进程 */printf("parent process PID = %d, a=%d,b=%d\n",getpid(),a,b);}else{perror("fork error");exit(1);}return 0;
}

在这里插入图片描述

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

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

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>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=%p\n",a,b,&a,&b);}else{perror("vfork error");exit(1);}return 0;
}

vfork_example.c

#include <stdio.h>
#include <unistd.h>
int main(void)
{int a=1,b=2;sleep(3);printf("child process,a=%d,b=%d,the address a =%p,b =%p\n",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, .../* pid_t *ptid, void *newtls, pid_t *ctid */ );
 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>int variable,fd;int do_something(void*arg)
{variable = 42;printf("in child process\n");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 %d\n",variable);clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);sleep(3);printf("The variable is now %d\n",variable);if(read(fd,&tempch,1)<1){perror("file read error");exit(1);}printf("we could read from the file\n");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 <stdio.h>
#include <stdlib.h>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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid = fork();if(pid==0){int count = 10;while(count--){printf("Child process : PID = %d; PPID : %d\n",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=%d\n",ret,WEXITSTATUS(status));}}else{printf("fork error\n");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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid = fork();if(pid==0){int count = 10;while(count--){printf("Child process : PID = %d; PPID : %d\n",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=%d\n",ret,WEXITSTATUS(status));}}else{printf("fork error\n");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, .../* (char  *) NULL */);int execlp(const char *file, const char *arg, .../* (char  *) NULL */);int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);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 <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#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] = '\0';//拆分命令myargv[0] = strtok(cmd, " ");int i = 1;while (myargv[i] = strtok(NULL, " ")){i++;}pid_t id = fork(); //创建子进程执行命令if (id == 0){//childexecvp(myargv[0], myargv); //child进行程序替换exit(1); //替换失败的退出码设置为1}//shellint status = 0;pid_t ret = waitpid(id, &status, 0); //shell等待child退出if (ret > 0){printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出码}}return 0;
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379593.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

银行账务转账系统(事务处理)

流程如下&#xff1a; 创建项目工程如下&#xff1a; transfer包下的代码如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils;pu…

【msdn wpf forum翻译】TextBox中文本 中对齐 的方法

原文链接&#xff1a;http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/49864e35-1dbf-4292-a361-93f1a8400558问题&#xff1a;TextBox中文本中对齐&#xff0c;使用 TextBox.HorizontalContentAlignment"Center"行不通&#xff08;TextBox.VerticalConte…

c语言 函数的参数传递示例_C语言中带有示例的remove()函数

c语言 函数的参数传递示例C语言中的remove()函数 (remove() function in C) The remove() function is defined in the <stdio.h> header file. remove()函数在<stdio.h>头文件中定义。 Prototype: 原型&#xff1a; int remove(const char* filename);Parameter…

使用ThreadLocal绑定连接资源(事务)

dao层代码如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils; import beyond.utils.MyDataSourceUtils;public class TransferDa…

算法---栈和队列

栈和队列1 栈栈的顺序存储栈的链式存储2 队列队列的顺序存储队列的链式存储3 栈和队列的应用用栈实现队列用队列实现栈最小栈1 栈 参考文章&#xff1a; https://zhuanlan.zhihu.com/p/346164833 https://zhuanlan.zhihu.com/p/120965372#:~:text%E6%A0%88%E6%98%AF%E4%B8%80%…

在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件

引言 这两天沉迷了Google SketchUp&#xff0c;刚刚玩够&#xff0c;一时兴起&#xff0c;研究了一下WebBrowser。 我在《WebBrowser控件使用技巧分享》一文中曾谈到过“我现在可以通过WebBrowser实现对各种Html元素的操控&#xff0c;唯独无法控制Html的上传控件”&#xff0c…

编写最简单的字符设备驱动

编写最简单的字符设备驱动1 编写驱动代码2 编写makefile3 编译和加载驱动4 编写应用程序测试驱动参考文章&#xff1a; linux驱动开发第1讲&#xff1a;带你编写一个最简单的字符设备驱动 linux驱动开发第2讲&#xff1a;应用层的write如何调用到驱动中的write 1 编写驱动代码…

Linux设备驱动开发---字符设备驱动程序

字符设备驱动程序1 主设备和次设备的概念设备号的注册和释放静态方法动态方法区别2 设备文件操作struct file_operations与struct file、struct inode关系3 分配和注册字符设备class_createcdev_adddevice_create4 字符设备驱动程序字符设备通过字符&#xff08;一个接一个的字…

Java中的异常栈轨迹和异常链

Java中允许对异常进行再次抛出&#xff0c;以提交给上一层进行处理&#xff0c;最为明显的例子为Java的常规异常。 常规异常&#xff1a;有Java所定义的异常&#xff0c;不需要异常声明&#xff0c;在未被try-catch的情况下&#xff0c;会被默认上报到main()方法。 Example: pu…

同步---信号量

信号量1 信号量2 驱动程序和测试程序3 内核的具体实现总结1 信号量 Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时&#xff0c;信号量会将其放到一个等待队列&#xff0c;然后让其睡眠&#xff0c;这时处理器去执行其他代码。当持有信号量的进…

算法---KMP算法

字符串1 KMP算法状态机概述构建状态转移1 KMP算法 原文链接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先约定&#xff0c;本文用pat表示模式串&#xff0c;长度为M&#xff0c;txt表示文本串&#xff0c;长度为N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

文件上传 带进度条(多种风格)

文件上传 带进度条 多种风格 非常漂亮&#xff01; 友好的提示 以及上传验证&#xff01; 部分代码&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋锁

1 自旋锁的基本概念 自旋锁最多只能被一个可执行线程持有&#xff0c;如果一个执行线程试图获得一个已经被使用的自旋锁&#xff0c;那么该线程就会一直进行自旋&#xff0c;等待锁重新可用。在任何时刻&#xff0c;自旋锁都可以防止多余一个的执行线程同时进入临界区。 Linu…

实习日志----4.播放时段参数设置

由于客户在下发广告时&#xff0c;一则广告可在多个时段播放&#xff0c;这就需要设置多个播放时段的参数。 但在这种情况下&#xff0c;我并不知道用户每次需要下发几个时段&#xff0c;所以前台不能设定死。 因此我要实现这么一个功能&#xff0c;让用户根据自己的需要来动态…

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

线程总结1 线程的实现线程创建线程退出线程等待线程清理2 线程的属性线程的分离线程的栈地址线程栈大小线程的调度策略线程优先级3 线程的同步互斥锁读写锁条件变量信号量线程是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源&#xff0c;例…

如何给Linux操作系统(CentOS 7为例)云服务器配置环境等一系列东西

1.首先&#xff0c;你得去购买一个云服务器&#xff08;这里以阿里云学生服务器为例&#xff0c;学生必须实名认证&#xff09; 打开阿里云&#xff0c;搜索学生服务器点击进入即可 公网ip为连接云服务器的主机 自定义密码为连接云服务器是需要输入的密码 购买即可 点击云服…

Linux系统编程---I/O多路复用

文章目录1 什么是IO多路复用2 解决什么问题说在前面I/O模型阻塞I/O非阻塞I/OIO多路复用信号驱动IO异步IO3 目前有哪些IO多路复用的方案解决方案总览常见软件的IO多路复用方案4 具体怎么用selectpollepolllevel-triggered and edge-triggered状态变化通知(edge-triggered)模式下…

c#中textbox属性_C#.Net中的TextBox.MaxLength属性与示例

c#中textbox属性Here we are demonstrating use of MaxLength property of TextBox. 在这里&#xff0c;我们演示了TextBox的MaxLength属性的使用。 MaxLength property of TextBox is used to set maximum number of character that we can input into a TextBox. Limit of M…

IIS7 MVC网站生成、发布

(1)生成。 确保System.Web.Mvc.dll在bin目录下 (2)发布网站到文件系统 (3)在IIS中为网站添加应用程序池&#xff08;一个虚拟目录&#xff0c;一个应用程序池&#xff09; (4)添加在默认网站下添加虚拟目录 &#xff08;5&#xff09;转换为应用程序 至此&#xff0c;部署完毕 …

C语言多维数组

文章目录多维数组数组名下标指向数组的指针作为函数参数的多维数组指针数组小结多维数组 如果某个数组的维数超过1&#xff0c;它就被称为多维数组&#xff0c;例如&#xff0c;下面这个声明&#xff1a; int matrix[6][10]创建了一个包含60个元素的矩阵。但是&#xff0c;它…