Linux系统编程中的进程操作,相关API函数介绍

一.进程的概念

代码,存储在磁盘中,程序员写出来夫人字符文件,是一系列用于指导计算机或设备执行特定任务或功能的指令或语句。

程序,也是存储在磁盘中,将字符文件编译得到的二进制文件,是一组完整的、组织良好的代码,用于执行一个或多个特定的任务。

进程,则是在内存中,它是程序代码在计算机上的一个动态执行过程,是正在运行的程序或者程序被加载到内存中,如果同一个程序,多次加载到内存中,产生的则是不同的进程。

1.查看进程的方式

在windows下: ctrl+atl+delete打开任务管理器查看

在Linux下终端输入:ps -ef或者ps -aux

2.进程的特点

动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的

并发性:任何进程都可以同其他进程一起并发执行

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;

异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进

3.进程的资源分配

        计算机给每个进程都会分配4G的虚拟内存,分为4个部分:堆区-需要手动开辟,手动释放;栈区-局部变量;数据段-全局区/静态区;代码段-代码/常量区。

        虽然理论上每个进程都可能有一个看似独立的、大小通常是4GB(或更大,取决于系统架构和配置)的虚拟地址空间,但实际上物理内存并不会如此分配。这是因为使用了虚拟内存技术,它允许操作系统为每个进程提供一个独立的地址空间,并通过页表等机制将这些虚拟地址映射到实际的物理内存地址上。

4.进程的状态

执行态:当下正在占用 cpu,cpu划分时间片;进程占有处理器正在运行。

就绪态:已经做好了可以执行的一切准备,等待cpu划分时间片;进程具备运行条件,等待系统分配处理器以便运行。

等待态:等待某事件发生;指进程不具备运行条件,正在等待某个事件的完成。

5.进程管理

暂停 恢复 杀死进程

Windows下在任务管理器中,点击鼠标右键,选择结束任务;

Linux下,先在终端输入指令ps -ef或者ps -aux来获取进程的PID号,再输入:kill -9 PID给这个PID号的进程发送9号信号,9号信号专门用来杀死进程。另外,快捷键ctrl+c或者ctrl+\可以杀死当前正在运行的进程而 ctrl+z表示暂停/挂起。暂停后只需在终端中输入fg,然后按下回车键即可恢复并执行。

进程间关系:父子关系。父进程:该进程是由谁引导产生的,谁就是该进程的父进程;

6.创建进程

第一步,运行一个可执行程序

第二步,在程序中使用fork或者vfork函数,可以创建进程。

二.进程相关 API 函数

1.获取进程的pidgetpid

1)获取当前进程的进程号

#include <sys/types.h>

#include <unistd.h>

pid_t getpid(void);

参数:无

返回值:pid_t类型的pid号

#include <stdio.h>  
#include <unistd.h>  
  
int main() {  
    pid_t pid = getpid();  
    printf("The PID of this process is: %d\n", pid);  
    return 0;  
}

2)获取当前进程的父进程的进程号

pid_t getppid(void);

参数:无

返回值:当前进程的父进程的进程号

在Unix和类Unix系统中,每个进程都有一个父进程,除了 init 进程(PID为1)它是系统启动时的第一个进程,它没有父进程。

#include <stdio.h>  
#include <unistd.h>  
  
int main() {  
    pid_t ppid = getppid();  
    printf("The PPID of this process is: %d\n", ppid);  
    return 0;  
}

2.结束进程

第一种,在main函数中运行return,可以结束进程(return的功能是退出当前函数);

第二种,结束进程,结束之前可以刷新缓冲区

        #include <stdlib.h>

        void exit(int status);

        参数:

                Status:进程退出时的状态,一般写0;

        返回值:无

第三种,结束进程,结束之前不刷新缓冲区

    直接使用:_exit(0);

拓展:延时函数

#include <unistd.h>

unsigned int sleep(unsigned int seconds);

参数

       Seconds:秒数

功能:阻塞性函数,在该函数的位置,延时seconds秒之后,解除阻塞

如sleep(1);  //代码在该位置阻塞1秒,1秒之后往下继续运行。

三.用函数创建进程

1fork创建子进程

#include <sys/types.h>

#include <unistd.h>

pid_t fork(void);

参数:无

返回值:在父进程中返回子进程的PID号(>0的值);在子进程中,返回0;失败返回-1;

        进程中运行到fork的时候,就已经有两个进程,原本的通过./a.out产生的进程,叫父进程,父进程内部,通过fork产生的叫做子进程。

       fork创建的子进程的特点:子进程复制父进程的所有资源(堆区,栈区,数据段,代码段),父子进程相互独立,互不干扰;子进程从fork的下一行开始运行;父子进程并发运行,谁先谁后看cpu调度。

#include <stdio.h>  
#include <unistd.h>  
  
int main() {  
    pid_t pid = fork();  
  
    if (pid < 0) 
    {  
        // fork 失败  
        perror("Fork failed");  
        return 1;  
    }  
  
    if (pid == 0) 
    {  
        // 子进程代码  
        printf("I am the child process, my PID is %d, my PPID is %d\n", getpid(), getppid());  
    } 
    else 
    {  
        // 父进程代码  pid>0
        printf("I am the parent process, my PID is %d, my child's PID is %d\n", getpid(), pid);  
    }  
  
    return 0;  
}

2vfork创建子进程

#include <sys/types.h>

#include <unistd.h>

pid_t vfork(void);

参数:无

返回值:在父进程中返回子进程的PID号;在子进程中返回0

创建的特点:子进程先运行,运行结束,父进程才能运行,子进程和父进程共享资源。

        vfork() 在子进程调用 exec() 或 exit() 之前,父进程和子进程是共享内存空间的,并且父进程会被阻塞,直到子进程调用 exec() 或 exit()。使得 vfork() 在某些情况下比 fork() 更高效,但也带来了更多的风险和不确定性。

        当父进程和子进程共享内存空间时,如果子进程修改了某些数据(如全局变量或静态变量),那么这些数据在父进程中也会被修改,这可能会导致意外的副作用。

#include <stdio.h>  
#include <unistd.h>  
#include <sys/types.h>  
  
int main() {  
    pid_t pid = vfork();  
  
    if (pid < 0) 
    {  
        // vfork 失败  
        perror("vfork failed");  
        return -1;  
    }  
  
    if (pid == 0) 
    {  
        // 子进程代码  
        printf("I am the child process, my PID is %d, my PPID is %d\n", getpid(), getppid());  
        _exit(0); // 子进程应使用 _exit 而非 exit,以避免额外的清理工作  
    } 
    else 
    {  
        // 父进程代码(注意:这里父进程会被阻塞,直到子进程调用 _exit())  
        printf("I am the parent process, my PID is %d, my child's PID is %d\n", getpid(), pid);  
        // 父进程继续执行...  
    }  
  
    return 0;  
}

3)特殊进程介绍

s-睡眠 r-运行 z-僵尸

父子进程:父进程拥有子进程的PID号;

0 号进程:操作系统的引导程序;

1 号进程/祖先进程:操作系统运行起来的第一个进程;

孤儿进程:父进程已经结束,子进程还在运行,此时就产生孤儿进程,孤儿进程会被PID为1的init进程(/lib/system/system –user)收养,进程退出之后帮其收尸

僵尸进程:子进程退出了,但是父进程太忙了,没空帮其回收资源,僵尸进程是一种几乎放弃了所有资源,但是占用一个进程号!

守护进程(Daemon Process):守护进程是Linux中的后台服务进程,也称为精灵进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程的特点是脱离终端在后台独立运行,其执行过程中的信息不在任何终端上显示。Linux的大多数服务器就是用守护进程实现的,比如Internet服务器inetd、Web服务器httpd等。

四.进程等待函数

1)等待任意子进程退出(wait)

阻塞性函数,等待任意子进程退出,只有子进程退出了该函数才能解除阻塞!

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *wstatus);

参数

       Wstatus:保存进程退出时的状态,一般写NULL

返回值:成功返回为其收尸的子进程的进程号

        此函数用于使父进程等待其子进程结束。这个函数会阻塞父进程的执行,直到有一个子进程结束为止。一旦某个子进程结束,wait() 就会返回该子进程的 PID,并且可以通过提供的 wstatus 参数获取子进程的退出状态。

2)等待指定子进程退出(waitpid)

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

参数

        pid:要等待哪一个子进程退出,可以写-1,表示等待任意子进程退出(类似于wait)

        wstatus:NULL;

        options:填0,阻塞等待子进程退出类似于wait,

                        填WNOHANG,不阻塞,如果子进程刚好退出,帮其收尸,没退出,不阻塞,直接                        就过去了,运行下一行代码;

返回值:如果options填0,返回为其收尸的子进程的进程号;

如果options填WNOHANG,如果子进程刚好退出,帮其收尸,返回为其收尸的子进程的进程号,如果子进程没退出,不阻塞,直接返回0;

例:waitpid(-1,NULL,0); == wait(NULL);

#include <stdio.h>  
#include <stdlib.h>  
#include <sys/wait.h>  
#include <unistd.h>  
/*父进程使用waitpid()等待特定的子进程(由fork()返回的PID)结束。
父进程会阻塞直到子进程退出,然后打印子进程的PID和退出状态。
如果子进程在waitpid()调用之前已经结束,那么waitpid()会立即返回并打印相应的信息。*/  
int main() 
{  
    pid_t pid, wpid;  
    int status;  
  
    pid = fork();  
  
    if (pid < 0) 
    {  
        perror("Fork failed");  
        exit(EXIT_FAILURE);  
    }  
  
    if (pid == 0) 
    {  
        // 子进程代码  
        printf("I am the child process, my PID is %d, my PPID is %d\n", getpid(), getppid());  
        sleep(5); // 让子进程暂停5秒  
        exit(EXIT_SUCCESS); // 子进程退出  
    } 
    else 
    {  
        // 父进程代码  
        printf("I am the parent process, my PID is %d, my child's PID is %d\n", getpid(), pid);  
  
        // 等待特定的子进程结束,不使用WNOHANG选项  
        wpid = waitpid(pid, &status, 0);  
  
        if (wpid == -1) 
        {  
            perror("waitpid");  
            exit(EXIT_FAILURE);  
        }  
  
        printf("Child %d exited with status %d\n", wpid, status);  
    }  
  
    return 0;  
}

五.system函数

在当前进程中运行其他进程

#include <stdlib.h>

int system(const char *command);

参数

        command:新的进程的启动命令,

        如果是自己写的可执行程序,填写绝对路径+可执行程序名system(“/home/wj/9206/0424/main”);

        如果是系统提供的,不用加路径。举例:system(“ls -l -a -i”);

六.exec 函数族

往往和fork/vfork配合使用,在当前进程中启动另外一个进程,当前的进程空间完全被取代,仅仅保留了原来的PID号

#include <unistd.h>

1)int execl(const char *pathname, const char *arg, .../* (char *) NULL */);

参数

       pathname:新的可执行程序的路径+可执行程序名,必须是绝对路径

        arg:可变传参,可以有1个或者多个

        arg1 -- 填可执行程序名

        arg2 -- 给可执行程序传递的参数

        ……

        最后必须以NULL结尾

2)int execlp(const char *file, const char *arg, .../* (char *) NULL */);

参数

        file:新的可执行程序的程序名,如果是自己的,写绝对路径;如果是系统的直接写可执行程序名即可

        arg: 可变传参, 可以有 1 个或者多个

        arg1 -- 填可执行程序名

        arg2 -- 给可执行程序传递的参数

        ……

        最后必须以 NULL 结尾

3)int execv(const char *pathname, char *const argv[]);

参数

        pathname:新的可执行程序的路径+可执行程序名,必须是绝对路径(不管是自己的还是系统的ls/clear等等)

        argv:指针数组,直接看如下举例

execl("/bin/ls","ls","-l","-a",NULL);改为execv,如下:

char *argv[] = {"ls","-l","-a",NULL};

execv("/bin/ls",argv);

4)int execvp(const char *file, char *const argv[]);

参数

        File:新的可执行程序的程序名,如果是自己的,写绝对路径;如果是系统的直接写可执行程序名即可

        argv:指针数组,直接看如下举例

execlp("ls","ls","-l","-a",NULL);改为execvp,如下:

char *argv[] = {"ls","-l","-a",NULL};

execvp("ls",argv);

vfork原来的特点:子进程没有自己的空间,和父进程共用空间,子进程先运行,退出之后父进程才能运行。

vfork+exec函数结合的特点:子进程将被分配空间,不再和父进程共享空间,父子进程并发运行。

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值