进程系统调用

简介: 进程系统调用

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和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;
    }
}


目录
相关文章
|
4月前
|
算法 Unix Linux
进程原理及系统调用
进程原理及系统调用
|
5月前
|
Linux 调度 Windows
【Linux系统化学习】探索进程的奥秘 | 第一个系统调用
【Linux系统化学习】探索进程的奥秘 | 第一个系统调用
|
6月前
|
程序员 Linux Shell
【CSAPP】进程控制 | 系统调用错误处理 | 进程状态 | 终止进程 | 进程创建 | 回收子进程 | 与子进程同步(wait/waitpid) | execve 接口
【CSAPP】进程控制 | 系统调用错误处理 | 进程状态 | 终止进程 | 进程创建 | 回收子进程 | 与子进程同步(wait/waitpid) | execve 接口
54 0
|
10月前
|
Unix Linux
进程原理及其系统调用(下)
进程原理及其系统调用
56 0
|
10月前
|
算法 Linux 调度
进程原理及其系统调用(上)
进程原理及其系统调用
93 0
|
Shell Linux C语言
【Linux进程】三、进程控制——fork()系统调用深度刨析
【Linux进程】三、进程控制——fork()系统调用深度刨析
230 0
【Linux进程】三、进程控制——fork()系统调用深度刨析
|
Linux C语言
Linux系统调用一、系统调用与C库函数的关系 —— 从进程虚拟地址空间和文件描述符的角度分析
Linux系统调用一、系统调用与C库函数的关系 —— 从进程虚拟地址空间和文件描述符的角度分析
169 0
Linux系统调用一、系统调用与C库函数的关系 —— 从进程虚拟地址空间和文件描述符的角度分析
|
存储 消息中间件 算法
操作系统学习笔记_2 中断和系统调用;进程和线程
学习自计算机科学单本&b站王道课程。
118 0
操作系统学习笔记_2 中断和系统调用;进程和线程
|
Android开发
【Android 逆向】Android 进程注入工具开发 ( 系统调用 | Android NDK 中的系统调用示例 )
【Android 逆向】Android 进程注入工具开发 ( 系统调用 | Android NDK 中的系统调用示例 )
251 0
【Android 逆向】Android 进程注入工具开发 ( 系统调用 | Android NDK 中的系统调用示例 )
|
Unix Linux 开发工具
linux strace-跟踪进程的系统调用或是信号产生情况,lstrace-跟踪己丑年调用库函数情况,进程跟踪调试命令
本工具可以用来做大多数排除,比如mount一个NFS,很慢,找不出原因,我们可以使用strace命令来跟中mount这个经常所有的调用过程。 strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。
2143 0