进程系统调用

news2024/12/23 19:58:54

进程系统调用


文章目录

  • 进程系统调用
  • fork()
    • 进程创建:fock()
    • fork函数
    • fork用法
      • 僵尸进程
      • 孤儿进程
    • vfork函数
    • vfork与fork区别
  • exec函数族
    • exec函数族-何时使用?
    • exec函数族语法
    • exec函数族使用区别
  • exit和_exit
    • _exit和exit的区别
  • wait和waitpid


fork()

进程创建:fock()

#include <sys/types.h> // 提供类型pid_t的定义
#include <unistd.h>

pid_t fork(void);

函数返回值
0: 子进程
子进程PID(大于0的整数):父进程
-1: 出错

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

int main(){

 
    pid_t pid;
        if((pid=fork())==-1){
        perror("fork");
        return -1;

    }else if(pid == 0){
        printf("child 子进程\n");
        printf("child process %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
        }else{
        printf("parent 父进程\n");
        printf("parent process %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
    }

    return 0;
}

在这里插入图片描述

fork()执行后当前进程会被克隆一遍,克隆之后子进程会有一个与原来相同的代码,父进程与子进程之间是独立调度的,即操作系统独立调度父进程和子进程,先调度谁,后调度谁有一定随机性,此处先调度了父进程,父进程看到的是子进程的pid,3518,然后父进程打印自己的pid和ppid,父进程运行完之后进程结束了,操作系统接着调度子进程,调度子进程的时候,也从fork()返回,但子进程看到的pid是0,所以子进程打印else内容,然后打印自己的pid和ppid

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

int main(){

 
    pid_t pid;
        if((pid=fork())==-1){
        perror("fork");
        return -1;

    }else if(pid == 0){
        printf("child 子进程\n");
        printf("child process %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
       子进程逻辑
      }else{
        printf("parent 父进程\n");
        printf("parent process %d,pid = %d,ppid = %d\n",pid,getpid(),getppid());
        父进程逻辑
 }

    return 0;
}

fork函数

使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:
进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
子进程与父进程的区别在于:
1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)
2、各自的进程ID和父进程ID不同
3、子进程的未决告警被清除;
4、子进程的未决信号集设置为空集

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glob = 6;
int main()
{
    int local;
    int pid;

    local = 88; 
    
    printf("parent[pid = %d]: before fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",
        getpid(), glob, &glob, local, &local );
    
    if((pid = fork()) < 0) {
        perror("fail to fork");
        return -1; 
    }   
    
    if(pid == 0) { /* child process */
        printf("child[pid = %d]: after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",
            getpid(), glob, &glob, local, &local );
        glob++;
        local++;    
        printf("child[pid = %d]: changed data after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",
            getpid(), glob, &glob, local, &local );
    }else { /* parent process */
        sleep(2);
        printf("parent[pid = %d]: after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",
            getpid(), glob, &glob, local, &local );
    }   
    /* return euqal to exit(0), but exit may cause a compile warning
     * due to main() is declared to return with an integter 
     */
    
    return 0;  
}

注意

 sleep(2)

目的防止父进程先执行,实现两个进程间的同步

在这里插入图片描述
在这里插入图片描述

虚拟地址相同,但实际物理地址不相同,所以修改后,父进程中变量的值没有被修改

该代码演示了fork函数的使用,fork函数可以复制一个新进程,并在新进程和原进程中运行不同的代码。下面是代码的详细分析:

  1. 包含需要的头文件:stdio.h,stdlib.h和unistd.h。
  2. 声明全局变量glob,并初始化为6。
  3. 定义main函数。
  4. 声明一个名为local的整型变量。
  5. 将变量local初始化为88。
  6. 打印出父进程的进程ID、全局变量glob的值和地址,以及变量local的值和地址。
  7. 如果fork函数返回值小于0,则表示fork失败,打印出错误信息并返回-1。
  8. 如果fork函数返回值等于0,则表示当前代码在子进程中运行。打印出子进程的进程ID、全局变量glob的值和地址,以及变量local的值和地址。
  9. 在子进程中,全局变量glob的值加1,变量local的值加1。
  10. 在子进程中,打印出子进程修改后的全局变量glob的值和地址,以及变量local的值和地址。
  11. 如果fork函数返回值大于0,则表示当前代码在父进程中运行。
  12. 在父进程中,调用sleep函数暂停2秒,以等待子进程修改变量。
  13. 在父进程中,打印出父进程修改前的全局变量glob的值和地址,以及变量local的值和地址。
  14. 最后,在主函数中返回0。

根据代码运行结果,可以看出:

  • 父进程在调用fork函数之前,输出了全局变量glob和变量local的值和地址。

  • 子进程在调用fork函数之后,复制了父进程的地址空间,并打印出全局变量glob和变量local的值和地址。在子进程中,这两个变量的值和地址与父进程相同。

  • 子进程修改了全局变量glob和变量local的值,并打印出它们修改后的值和地址。在子进程中,这两个变量的值和地址已经发生了变化。

  • 父进程在等待2秒钟后继续执行,并打印出全局变量glob和变量local的值和地址。在父进程中,这两个变量的值和地址没有发生变化。

这个结果的原因是,子进程复制了父进程的地址空间,但是子进程和父进程有不同的地址空间。因此,它们可以独立地访问和修改自己的变量。在子进程中修改全局变量和局部变量的值并不会影响父进程的值。

fork用法

僵尸进程

  1. 父进程 Process A 创建子进程 Process B,当子进程退出时会给父进程发送信号 SIGCHLD; 2. 如果父进程没有调用 wait 等待子进程结束,退出状态丢失,转换成僵死状态,子 进程会变成一个僵尸进程
#include <sys/types.h> 
#include <unistd.h> 

/* create a ZOMBIE 
 * ps -ax | grep a.out to show the zombie 
 */ 
int main() 
{ 
    if(fork()) { 
        // 父进程

        while(1){ 
            sleep(1); 
        }
    }   
    // 子进程
} 

在这里插入图片描述

子进程啥事不干,退出,父进程循环不调用wai回收子进程

表示为僵尸进程
在这里插入图片描述

孤儿进程

如 果 父 进 程 退 出 , 并 且 没 有 调 用 wait 函数 , 它 的 子 进 程 就 变 成 孤 儿 进程 , 会 被 一个 特 殊 进 程 继 承 , 这 就 是 init 进程, init 进程 会 自 动 清理 所 有 它 继 承 的 僵 尸 进 程 。
16.04做了修改由upstart继承
ubuntu14 由init继承】

#include <sys/types.h> 
#include <unistd.h> 


int main() 
{ 
    if(fork()) { 
        // 父进程


    }else{ 
        // 子进程

        while(1){ 
            sleep(1); 
        }
    }   
} 

在这里插入图片描述
在这里插入图片描述

vfork函数

由于fork完整地拷贝了父进程的整个地址空间,因此执行速度是比较慢的。
为了提高效率, Unix系统设计者创建了vfork。
vfork也创建新进程,但不产生父进程的副本。
它通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才拷贝父进程。
这就是著名的“写操作时拷贝” (copy-on-write)技术

vfork与fork区别

关键区别一: vfork 直接使用父进程存储空间,不用拷贝 关键区别二:vfork 保证子进程先运行,当子进程调用 exit 退出后,父进程才执行

我们要创建一个进程,谁来负责fork?
shell
我们在shell下执行的程序,进程又是如何创建装载的?
函数族
strace

exec函数族

exec函数族提供了一种在进程中启动另一个程序执行的方法。
它可以根据指定的文件名或目录名找到可执行文件, 并用它来取代原调用进程的数据段、 代码段和堆栈段。
在执行完之后, 原调用进程的内容除了进程号外, 其他全部都被替换了。
可执行文件既可以是二进制文件, 也可以是任何Linux下可执行的脚本文件。

exec函数族-何时使用?

当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样

exec函数族语法

#include <unistd.h>

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

函数返回值 :-1:出错

exec函数族使用区别

可执行文件查找方式
表中的前四个函数的查找方式都是指定完整的文件目录路径,
而最后两个函数(以p结尾的函数)可以只给出文件名, 系统会自动从环境变量“$PATH” 所包含的路径中进行查找。
参数表传递方式
两种方式:
逐个列举或是将所有参数通过指针数组传递
以函数名的第五位字母来区分,
字母为“l”(list)的表示逐个列举的方式;• 字母为“v”(vertor)的表示将所有参数构造成指针数组传递,其语法为char *const argv[]
环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。
这里,以“e”(Enviromen)结尾的两个函数execle、 execve就可以在envp[]中传递当前进程所使用的环境变量

eg
p
而最后两个函数(以p结尾的函数)可以只给出文件名, 系统会自动从环境变量“$PATH” 所包含的路径中进行查找。
l
字母为“l”(list) 表示参数逐个列举的方式;
v
字母为“v”(vertor)的表示将所有参数构造成指针数组传递,其语法为char *const argv[]

echo $PATH

在这里插入图片描述

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

int main()
{
    if (execlp("ps", "ps", "-ef", NULL) < 0)
    {    
        perror("execlp error!");
    }   
    return 0;
}

在这里插入图片描述

#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>

int main(int argc,char **argv)
{
    pid_t pid;
    
    printf("PID = %d\n",getpid());
    
    pid=fork();
    if(pid==0)
    {   
        //子进程
        execvp("ls",argv);    // ./r -ef        
//      execv("/bin/ls",argv);  ./run -l 
//      execl("/bin/ls","ls","-l","/",NULL);
        //指令路径 指令 参数   目录   空
//      execlp("ls","ls","-al","/",NULL);
        sleep(10);
    }else if(pid!=-1)
    {   
        //父进程        
        printf("\nParrent porcess,PID = %d\n",getpid());
    }else
    {   
        printf("error fork() child proess!");
    }   
    return 0 ; 
}

在这里插入图片描述
在这里插入图片描述

exit和_exit

所需头文件 exit: #include <stdlib.h>_exit: #include <unistd.h>函数原型 exit: void exit(int status);_exit: void _exit(int status);函数传入值 status是一个整型的参数,可以利用这个参数传递进程结束时的状态。通常0表示正常结束;其他的数值表示出现了错误,进程非正常结束。在实际编程时,可以用wait系统调用接收子进程的返回值,进行相应的处

在这里插入图片描述

_exit和exit的区别

_exit() :
直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()
在这些基础上作了一些包装,在执行退出之前加了若干道工序。 exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的"清理I/O缓冲"一项。

int main(){
    printf("Using exit...\n");
    printf("This is the end");
    exit(0);
}

在这里插入图片描述

#include <stdio.h>
#include <inistd.h>

int main(){
    printf("Using _exit...\n");
    printf("This is the end");
    _exit(0);
}

在这里插入图片描述

wait和waitpid

wait和waitpid
wait函数
调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束, wait函数会立即返回。
waitpid函数
功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)

waitpid.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

/* wait method: */
 /* waitpid*/
int main(int argc, char **argv)
{
    pid_t pid;

    printf("parent[pid=%d] is born\n", getpid());
         
    if (-1 == (pid = fork())) {
        perror("fork error");
        return -1;
    }
    
    if (pid == 0){//子进程
        printf("child[pid=%d] is born\n", getpid());
        sleep(5);
        printf("child is over\n");
    }
    else{ //parent  父进程
        pid_t pid_w;
        
        while((pid_w = waitpid(pid, NULL, WNOHANG)) == 0) {
            printf("parent wait w/o HAND and returns with 0\n");
            sleep(1);
        }
        printf("waitpid returns with pid = %d.\n", pid_w);
        
        printf("father is over\n");
    }
    
    return 0;
}

在这里插入图片描述

wait_z.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

/* no wait and let zombie be */

int main(int argc, char **argv)
{
    pid_t pid;
    
    printf("parent[pid=%d] is born\n", getpid());
         
    if (-1 == (pid = fork())) {
        perror("fork error");
        return -1;
    }
    
    if (pid == 0){
        printf("child[pid=%d] is born\n", getpid());
        sleep(10);
        printf("child is over\n");
    }
    else{
        while(1){};
    }
        
    return 0;
}

在这里插入图片描述

wait.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

/* wait */
        
int main(int argc, char **argv)
{
    pid_t pid;
    int status;

    printf("parent[pid=%d] is born\n", getpid());
         
    if (-1 == (pid = fork())) {
        perror("fork error");
        return -1;
    }
    
    if (pid == 0){
        printf("child[pid=%d] is born\n", getpid());
        sleep(10);
        printf("child is over\n");
        exit(7);
        return 123;
    }
    else{
        pid_t pid_w;
        
        pid_w = wait(&status);//等待子进程退出 0x7b00
        if (pid_w < 0) {
            perror("wait error");
            return 1;
        }
        printf("status=%x \n",status);
        if (WIFEXITED(status)) {
            //正常退出
            status = WEXITSTATUS(status);//提取返回信息
            printf("wait returns with pid = %d. return status is %d\n", pid_w, status);
        } else {
            //非正常退出
            printf("wait returns with pid = %d. the child is terminated abnormally\n", pid_w);
        }        
        printf("father is over\n");        
        return 0;
    }
}

在这里插入图片描述

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

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

相关文章

记录一次WIN11开机在登录页面循环的问题

记录一次由于未进行win密码设置&#xff0c;导致开机后卡在登录界面无法登录进去的问题。最后完美解决了。 1. 背景 开机后&#xff0c;显示用户登录界面&#xff0c;但是和以往不同&#xff0c;没有了密码输入框&#xff0c;只有一个“登录”按钮孤零零地显示在屏幕中间&…

Flink从入门到精通系列(四)

5、DataStream API&#xff08;基础篇&#xff09; Flink 有非常灵活的分层 API 设计&#xff0c;其中的核心层就是 DataStream/DataSet API。由于新版本已经实现了流批一体&#xff0c;DataSet API 将被弃用&#xff0c;官方推荐统一使用 DataStream API 处理流数据和批数据。…

每天5分钟快速玩转机器学习:贝叶斯算法的局限性

本文重点 贝叶斯算法的应用很广泛,其中最经典的应用就是垃圾邮件的分类,本节课程通过垃圾邮件的例子来看一下贝叶斯算法存在的一些问题,我们应该如何解决它? 垃圾邮件分类 给定一封电子邮件,我们如何判断这封电子邮件是垃圾邮件还是正常邮件,这是机器学习中的二分类问…

corn表达式

简单理解corn表达式&#xff1a;在使用定时调度任务的时候&#xff0c;我们最常用的&#xff0c;就是cron表达式了。通过cron表达式来指定任务在某个时间点或者周期性的执行。cron表达式配置起来简洁方便&#xff0c;无论是Spring的Scheduled还是用Quartz框架&#xff0c;都支持…

JavaWeb14-线程池

目录 1.传统线程的缺点 2.线程池的定义 3.线程池的优点 4.线程池的创建/使用&#xff08;2类7种&#xff09; 4.1.通过Executors&#xff08;执行器&#xff09;自动创建&#xff08;6种&#xff09; ①Executors.newFixedThreadPool&#xff1a;创建⼀个固定⼤⼩的线程池…

哈希冲突

为什么会有哈希冲突&#xff1f;哈希表通过哈希函数来计算存放数据&#xff0c;在curd数据时不用多次比较&#xff0c;时间复杂度O&#xff08;1&#xff09;。但是凡事都有利弊&#xff0c;不同关键字通过相同哈希函数可能计算出来相同的存放地址&#xff0c;这种现象被称为哈…

JVM的内存回收及常见算法

什么样的对象应该被回收&#xff1f;某个对象不再被栈直接或间接地引用&#xff0c;此时就应该被回收了。o被指向null的时候&#xff0c;new Object()创建的对象就不在被栈引用了&#xff0c;可以被回收。p1和personList均不再指向第一个Person对象的时候&#xff0c;第一个Per…

【小墩墩学Android】开发常见问题FAQ之Gradle更新

文章目录1、简介1.1 Android简介1.2 Gradle简介1.3 Gradle的配置文件1.3.1 应用模块的 build.gradle1.3.2 项目的 settings.gradle1.3.3 gradle-wrapper.properties2、Gradle文件下载失败2.1 手动下载gradle2.2 配置本地gradle2.3 配置国内镜像3、repositories配置国内源3.1 单…

蓝桥杯三月刷题 第八天

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;分数&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;回文日期&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;迷宫&#x1f914;一、思路:&#x1f60e;二、代码&a…

LVGL学习笔记18 - 表Table

目录 1. Parts 1.1 LV_PART_MAIN 1.2 LV_PART_ITEMS 2. 样式 2.1 设置行列数 2.2 设置单元格字符串 2.3 设置单元格宽度 2.4 设置表格高度和宽度 2.5 设置字符串颜色 2.6 设置边框颜色 2.7 设置背景颜色 3. 事件 4. CELL CTRL 表格是由包含文本的行、列和单元格构…

【Git】Git仓库初始化

Git本地仓库初始化 1.将本地代码上传至远程新建仓库 1.1.建立远程仓库 1.2.初始化本地代码仓库 第一步&#xff1a;进入本地代码目录 cd /代码路径 第二步&#xff1a;初始化仓库(执行如下命令) git init 第三步&#xff1a;将本地全部文件添加到本地缓冲区(执行如下命令)…

循环神经网络原理及实现(二):循环神经网络复现

专栏&#xff1a;神经网络复现目录 循环神经网络 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种神经网络结构&#xff0c;其主要特点是网络中存在循环连接&#xff0c;使得网络具有记忆功能&#xff0c;可以处理序列数据。在传统神经网…

autoxjs

文章目录autojs一、工具二、使用步骤1.手机设置开发模式并打开usb调试2.安装scrcpy3. 安装autoxjs4. vscode插件使用auto 入门语法总结autojs autojs 目前作者已经跑路了&#xff0c;转为用社区的autoxjs&#xff0c;官网地址&#xff1a;http://doc.autoxjs.com/#/ 一、工具 …

Echart的使用初体验,Echarts的基本使用及语法格式,简单图表绘制和使用及图例添加【学习笔记】

Echart&#xff1f; ECharts 是一个使用 JavaScript 实现的开源可视化库&#xff0c;涵盖各行业图表&#xff0c;满足各种需求。 ECharts 遵循 Apache-2.0 开源协议&#xff0c;免费商用。 ECharts 兼容当前绝大部分浏览器&#xff08;IE8/9/10/11&#xff0c;Chrome&#xf…

【打造家庭服务器系列01】无桌面版Ubuntu 22.04 连接wifi

一、背景 最近有一台笔记本一直放在哪没用了&#xff0c;就想着拿来做个服务器用吧。 如何安装Ubuntu系统&#xff0c;大家可以百度搜索一下很多。 主要分三步&#xff1a; 制作U盘启动盘&#xff08;推荐使用rufus工具&#xff0c;轻量方便&#xff09;设置BIOS引导 &#x…

java——代理

什么是代理&#xff1a; 给目标对象一个代理对象&#xff0c;由代理对象控制着对目标对象的引用 为什么使用代理&#xff1a; ①&#xff1a;功能增强&#xff1a;通过代理业务对原有业务进行增强 ②&#xff1a;用户只能同行过代理对象间接访问目标对象&#xff0c;防止用…

About What Is a DBA?

1.Evaluating a DBA Job Offer Here are some useful questions to ask: • Does the company offer regular training for its DBAs to learn new DBMS features and functionality? What about training for related technologies such as programming, networking, e-bus…

[NIPS 2017] Improved Training of Wasserstein GANs (WGAN-GP)

Contents IntroductionDifficulties with weight constraintsCapacity underuseExploding and vanishing gradientsGradient penaltyReferencesIntroduction WGAN 增加了 GAN 模型训练的稳定性,但有时仍然会有生成质量不高或难以收敛的问题。作者发现上述问题经常是由 WGAN 中…

保障信息安全:使用PyZbar库识别二维码图片可以快速获取二维码中的信息,保障信息安全。

目录 简介&#xff1a; 源代码&#xff1a; 源代码说明&#xff1a; 效果如下所示&#xff1a; 简介&#xff1a; 不用摄像头识别二维码可以应用在以下场景&#xff1a; 批量处理二维码图片&#xff1a;可以在服务器上使用PyZbar等库来批量处理二维码图片&#xff0c;例如读…

Nginx 配置实例-负载均衡

一、实现效果 浏览器地址栏输入地址 http://192.168.137.129/edu/a.html&#xff0c;负载均衡效果&#xff0c;将请求平均分配到8080和8081两台服务器上。 二、准备工作 1. 准备两台tomcat服务器&#xff0c;一台8080&#xff0c;一台8081 (具体操作如下两个链接) Nginx配置实…