Linux进程及相关命令

文章目录

                一、程序

                二、进程

                三.和进程相关的命令PS

                四、静态库和动态库

                五、main程序参数

                 六、bash

                七、Windows和Linux编写c语言的区别

                八、return和exit的区别

                九、PCB

                十、虚拟内存和物理内存

                十一、页表

                十二、逻辑地址和物理地址

                十三、复制进程的系统调用fork()

                十四、 写时拷贝

                十五、写时拷贝例题

                十六、僵死进程和孤儿进程

                十七、进程的替换

一、程序

程序:指令   +   数据

二、进程

进程:运行中的程序,资源分配的基本单位。

操作系统(OS)通过PCB(进程控制块)管理进程

PCB的作用是记录当前的进程的运行状态        PCB之间是使用双向链表进行管理

PID是OS区分进程的标识

一个PCB对应一个PID(进程号)对应一个进程

创建进程: 先创建PCB,后创建进程实体

销毁进程: 先释放进程实体,再释放PCB

并行处理:在同一时刻,能够同时执行多个进程,每核CPU在每一时刻执行一个进程,所以要同时进行多个进程的运行,需要多核CPU。

串行处理:先处理完一个进程,再处理另一个进程

并发处理:在某一时间段,需要处理多个任务(进程)。单核CPU,在某一时刻只能处理一个任务,多个进程通过进程的切换,进行进程的执行。

三.和进程相关的命令PS

3.1 unistd.h库文件中的sleep函数

sleep(n) 睡眠n秒

3.2程序后台执行&

./main 默认在前台执行程序

./main & 在后面加上& ,该程序会在后台运行

3.3查看进程信息PS命令

ps               查看当前终端上运行的进程信息

ps   -e         显示系统所有进程信息

ps   -f          显示当前终端更多的进程属性信息 一般使用ps -ef

ps   -L         显示当前终端进程中的线程ID 即线程LWP信息

3.4 ps -ef 中各个类型的意思:

UID :执行该进程的用户ID

PID :进程号,一个进程有唯一的PID号

PPID :父进程的进程号

C :CPU的使用率

STIME :进程启动时间

TTY :终端的类型

:若进程的运行与终端无关,则会显示?

pts/0 :表示由网络连接主机

tty1~tty6 :虚拟终端

TIME :进程运行时间

CMD :进程启动时使用的命令

3.5结束进程kill命令

kill 进程PID号   结束该PID号对应的进程    

挂起的程序该命令不能结束,需要使用强制结束

kill -9 进程PID号 强制结束该进程

kill -STOP 进程PID号 挂起该进程

3.6 bg fg 前后台进程切换

bg %任务号 唤醒被挂起的程序调到后台执行

fg %任务号 将后台的进程调到前台来运行

或者bg/fg 直接任务号

任务号不是进程号

3.7 runlevel 查看系统运行级别

数字含义:

0 关机

1 单用户级别

2 多用户无网络级别

3 多用户文本界面

4 无定义、自定义界面

5 图形化界面

6 重启

使用方法:

init 对应的数字

四、静态库和动态库

4.1库文件

将函数封装在一起编译后供自己或者别人调用。

好处: 编译后的库文件看不到源代码,可以保密

4.2静态库和动态库

Windows系统下的静态库名字为.lib,动态库名字为.dll

Linux系统下的静态库名字为.a,动态库名字为.so。一般放在/lib和/usr/lib文件下。

Linux系统的静态库的命名规则为 libxxx.a,动态库的命名规则为 libxxx.so

4.3创建静态库以及ar命令

第一步:

将需要封装成库文件的.c文件编译成.o文件

例子: gcc -c add.c mul.c

第二步:

创建静态库文件

例子: ar crv libfoo.a add.o mul.o

ar命令参数说明:c:创建库 r:将方法添加到库中 v:显示过程

静态库的名字必须以lib开头,.a结尾。中间为自己起的名字

第三步:

使用静态库和主程序生成可执行程序

例子: gcc -o main main.c -L . -l foo

参数:-L 指定库的存储路径,即-L后为存放静态库的路径

参数:-l 指定库的名称(不需要前面的lib和扩展名.a

第四步:

生成main可执行文件后运行该文件

静态库的缺点:在多个进程调用同一个静态库时,每个进程都会为该静态库提供内存。为了解决这个问题,使用到动态库

4.4 创建动态库(共享库)

第一步:

将需要封装成库文件的.c文件编译成.o文件

例子: gcc -c add.c mul.c

第二步:

创建动态库文件

例子: gcc -shared -fPIC -o libfoo.so add.o mul.o

动态库的名字必须以lib开头,.so结尾。中间为自己起的名字

该步骤结束后可生成动态库libfoo.so文件

第三步:

使用动态库和主程序生成可执行程序

gcc -o main main.c -L . -l foo

参数:-L 指定库的存储路径,即-L后为存放动态库的路径

参数:-l 指定库的名称(不需要前面的lib和扩展名.a

第四步:

生成main可执行文件后运行该文件

4.5 查看执行程序使用了哪些共享库的ldd命令

将使用了共享库的执行文件生成,使用ldd 可执行文件名 进行查看

4.6动态库存在的问题:

在上面三步完成后执行生成的main文件会显示链接错误,原因是系统默认只会去存储库的标准位置(/lib 或 /usr/lib 等)加载。

解决方法1:是将生成的动态库文件拷贝到/usr/lib下,再执行即可。

解决方法2:修改环境变量,使得动态链接路径由原来的/usr/lib改为自定义路径。

添加环境变量命令: export LD_LIBRARY_PATH=.

=号后的 . 为当前路径。

查看环境变量命令: echo $LD_LIBRARY_PATH

删除环境变量命令: unset LD_LIBRART_PATH

4.7静态库和动态库的区别

静态库在链接时将用到的方法包含到最终生成的可执行程序中,而共享库不包含,只做标记,在运行程序时,才动态加载,所以共享库体积小。

静态库就是在编译过程中一些目标文件的集合。静态库在程序链接的时候使用,链接器会将程序中使用到函数的代码从库文件中拷贝到应用程序中。一旦链接完成,在执行程序的时候就不需要静态库了。

由于每个使用静态库的应用程序都需要拷贝所用函数的代码,所以静态链接的文件会比较大。

相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中而只是作些标记。然后在程序开始启动运行的时候,动态地加载所需模块,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

五、main程序参数

int main(int agrc,char* argv[],char* envp)
{
    return 0;
}

argc的含义: 主函数参数个数

argv的含义: 字符数组 存放参数内容

envp的含义: 字符数组 存放环境变量(从父进程继承下来的环境变量)

环境变量envp 参数内容argv 都是以NULL结尾

六、bash

用户只能操作计算机的硬件通过shell操作内核

shell是用户和Linux内核交互的接口程序,即终端。

shell通过$PATH环境变量寻找可执行程序,被分解为系统调用并传递给内核执行

bash —> born again shell bash是shell中的一种

七、Windows和Linux编写c语言的区别

Winodws

定义数组时,括号内必须为常量或者常变量;

没有给数组初始化,打印值为随机值;

char* str="hello";语句错误。必须加const

没有行缓冲

Linux

定义数组时,括号内可以为常量也可以为变量;

没有给数组初始化,打印值为0;

char* str="hello";语句正确

有行缓冲

#include
#include
int main()
{
    printf("hello");
    sleep(5);
    return 0;
}

hello后面没有加\n时,执行程序后,会先睡眠5秒钟,再打印hello。原因就是:linux系统存在行缓冲,只有碰到\n(行缓冲)才会输出

对于使用write文件操作函数打印时,不会经过缓冲区,是直接进行打印。write(1,"hello",5); 1为标准输出的文件描述符。

7.1刷新缓冲区:

1.程序结束前会刷新缓冲区(return 0 exit 等等)

2.碰见\n会刷新缓冲区

3.碰见fflush会刷新缓冲区(使用fflush(stdout)函数)

4.缓冲区存放满了也会刷新缓冲区

八、return和exit的区别

return

关键字,语言应用。

当前功能的结束

exit (结束程序前会刷新缓冲区)

函数调用,系统级别。涉及栈的开辟

进程的退出

_exit (只结束程序**不刷新缓冲区**)

内核级别函数(只结束程序)

exit函数内部实现调用_exit进程终止

fflush(stdout); 和_exit(0); 等价于 exit(0);

九、PCB

即是进程控制块(Processing Control Block),是进程存在的唯一标志。用来描述进程的属性信息。OS是根据PCB来对并发执行的进程进行控制和管理的。进程是操作系统资源分配和调度的基本单位。

9.1程序的运行过程:

第一步:将代码和数据通过编译链接过程加载到内存的代码段(.text)和数据段(.data)

第二步:cpu(控制器)从内存上取指令进行执行

第三步:循环上面操作,取指令执行

9.2进程状态:

Linux进程及相关命令_第1张图片

十、虚拟内存和物理内存

10.1物理内存:

物理内存(Physical memory)是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。内存主要作用是在计算机运行时为操作系统和各种程序提供临时储存。

10.2虚拟内存:

虚拟内存是计算机系内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

10.3虚拟内存相对于直接使用物理内存的有优点

1) 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间来回传送数据,使得能够运行比内存大的多的进程。

2) 它为每个进程提供了一致的地址空间,从而简化了存储器管理

3) 它保护每个进程的地址空间不被其他进程破坏

十一、页表

页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表。

十二、逻辑地址和物理地址

12.1物理地址和逻辑地址概念:

物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。

逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一

。逻辑地址是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。

12.2逻辑地址和物理地址的转化:

逻辑地址对一页的大小(一般为4k)做除法,得商为该逻辑地址的页号;余数为逻辑地址的偏移量。查看页表,找到该逻辑地址的页号对应的物理地址的页号,物理地址的页号加上偏移量就是该逻辑地址映射的物理地址。

十三、复制进程的系统调用fork()

13.1 fork函数的返回值的含义:

返回值大于0:表明是父进程

返回值等于0:表明是子进程

13.2 子程序的运行

在程序运行到fork()处时,会复制子进程,子进程会从fork()之后开始执行,不会从头开始执行

十四、 写时拷贝

14.1概念:

写时拷贝是一种可以推迟甚至免除拷贝数据的技术,内核此时并不是复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。

14.2exce系统调用

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程此后会和exce系统调用相结合。

在fork函数之后exec之前,两个进程用的是相同的物理空间(内存),子进程的代码段,数据段,堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再给子进程分配相应的物理空间。

若不存在exec,那么内核只会给子进程的数据段,堆栈段分配相应的物理空间(至此两者都是各自的进程空间,互不影响),而代码段则共享父进程的物理空间(因为父子进程代码完全相同)。

若存在exec,由于进程被替换为其他程序,两者执行的代码不同,子进程代码段也会分配单独的物理空间。

fork之后内核会通过将子进程放在队列的前面,先让子进程执行,以免父进程执行导致写时拷贝,而后子进程执行exec系统调用,无意义的写时拷贝造成效率的下降

十五、写时拷贝例题

15.1.1面试题开始:

#include
#include
#include
int main()
{
    int n = 0;
    char* s = NULL;
    pid_t pid = fork();//返回值大于0时表明是父进程,返回值等于0时,表明是子进程
    //断点一
    if(pid == 0)
    {
        n = 3;
        s = "child";
    }
    else
    {
        n = 7;
        s = "parent";
    }
    //段点二
    for(int i = 0 ;i < n; i++)
    {
        printf("pid=%d,ppid=%d,s=%s,&n=%p\n",getpid(),getppid(),s,&n);
        //getpid()获取当前进程的pid,getppid()获取当前进程父进程的pid
        sleep(1);
    }
    return 0;
}

在程序执行到断点一处时,该进程会产生一个与之相同的子进程。此时,该子进程和其父进程的逻辑地址相同(即子父进程的变量n和变量s的逻辑地址是一样的),并且共享同一块物理内存(即子父进程的变量n和变量s的物理地址也是一样的)。

但是在程序执行到断点二处时,子父进程各自里的变量n和变量s发生改变。此时,该子进程和其父进程的逻辑地址相同(即子父进程的变量n和变量s的逻辑地址是一样的),但是子进程不再和父进程共享同一块物理内存,系统会给子进程另一块物理内存去存储变量n和变量s。

之所以改变后的子父进程的逻辑地址仍然相同,是因为,子进程是除了pid号和fork返回值以及个别与父进程不同外,其他都是完全复制父进程的,包括逻辑地址,缓存区等等。

注意:两个进程的逻辑地址相同,并不意味着映射同一块物理空间,只能说明两个进程的偏移量相同,因为不同进程的页目录首地址不同

上述中所说的逻辑地址相同指的是打印的逻辑地址值是相同的,不是逻辑内存相同。不同进程的逻辑内存是相互独立的。

在Linux系统中,父进程的父进程是bash

15.1.2面试题结束:

15.2.1笔试题开始

#include
#include
#include
//示例1
int main()
{
    printf("A\n");
    fork();
}
//示例2
int main()
{
    printf("A");
    fork();
}

示例1结果:打印一个A

分析:父进程先将一个A输入到缓冲区中,碰到'\n'后刷新缓冲区,父进程打印一个A。父进程碰到fork()后复制一个子进程,子进程会执行fork()之后的代码。最后父子进程结束,结果打印一个A

示例2结果:打印两个A

分析:父进程先将一个A输入到缓冲区中,因为没有刷新缓冲区的标志,继续执行。父进程碰到fork()后复制一个子进程,因为复制会和父进程一样,子进程的缓冲区也存在一个A。父子进程继续执行,然后进程结束。刷新父子进程的缓冲区,打印两个A

//示例3
int main()
{
    fork();
    printf("A\n");
}
//示例4
int main()
{
    fork();
    printf("A");
}

示例3结果:打印两个A

分析:父进程碰到fork()后复制一个子进程,子进程会执行fork()之后的代码。父子进程各自将一个A输入到缓冲区中,碰到\n后,父子进程刷新缓冲区,各自打印一个A,最后打印两个A

示例4结果:打印两个A

分析:父进程碰到fork()后复制一个子进程,子进程会执行fork()之后的代码。父子进程各自将一个A输入到缓冲区中,都没有碰到刷新缓冲区标志,继续执行。进程结束刷新缓冲区,打印两个A

//示例5
int main()
{
    for(int i = 0;i <2; i++)
    {
        fork();
        printf("A\n");
    }
}
//示例6
int main()
{
    for(int i = 0;i <2; i++)
    {
        fork();
        printf("A");
    }
}

示例5结果:打印六个A

分析: (对该类题的技巧:先看完父进程再看子进程)

对父进程: 父进程进入循环,此时i=0碰到fork()后复制一个子进程1,子进程1当前的i=0。父进程向缓冲区输入A,碰到"\n",刷新缓冲区,打印一个A。然后继续循环,此时i=1碰到fork()后再复制一个子进程2,子进程2当前的i=1。父进程继续和刚才一样打印一个A,之后父进程再循环i=2不满足条件,跳出循环,父进程结束。

对子进程1: 子进程1从fork()之后开始执行,子进程1向缓冲区输入A,碰到"\n",刷新缓冲区,打印一个A。继续循环,此时i=1。碰到fork()后再复制一个子进程3,子进程3当前的i=1。子进程1继续和刚才一样打印一个A,之后子进程1再循环i=2不满足条件,跳出循环,子进程1结束。

对子进程2: 子进程2从fork()之后开始执行,子进程2向缓冲区输入A,碰到"\n",刷新缓冲区,打印一个A。继续循环,子进程2此时i=2不满足条件,跳出循环,子进程2结束。

对子进程3:子进程3从fork()之后开始执行,子进程3向缓冲区输入A,碰到"\n",刷新缓冲区,打印一个A。继续循环,子进程3此时i=2不满足条件,跳出循环,子进程3结束。

最终,父进程打印两个A,子进程1打印两个A,子进程2打印一个A,子进程3打印一个A。总共打印六个A。

示例6结果:打印八个个A

分析:与上面基本相同,唯一要注意的是该程序会先将A输入缓冲区,在进程复制时,缓冲区中的内容也会复制进去,在程序结束时,才会刷新缓冲区,打印缓冲区的内容。

一个进程对应一个缓冲区,进程复制时,缓冲区及其中的内容也会进程复制

//示例6
int main()
{
    if(fork()||fork())
    {
        printf("A\n");
    }
}

示例6结果:打印两个A

分析:对父进程fork()复制子进程1,父进程返回值大于零,逻辑值为真。真逻辑或任何都为真,所以不执行第二个fork(),然后打印A。对于子进程1,其第一个fork()返回值为假,需要判断第二个fork(),对于子进程1来说,第二个fork()返回值大于0,为逻辑真,执行打印A。执行子进程1第二个fork(),会复制子进程2,由于复制子进程2的第一个fork()和子进程1一样为逻辑假,子进程2为子进程1的子进程,所以其第二个fork()返回值等于0,为逻辑假。所以不满足if语句,不执行打印A。最终父进程打印一个A,子进程1打印一个A。总共打印两个A

15.2.2笔试题结束

十六、僵死进程和孤儿进程

16.1僵死进程概念:

子进程先于父进程结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵死进程。如果父进程先退出,子进程退出后init会回收其占用的相关资源。

Linux系统中,一个进程结束了,若他的父进程没有等待它(调用wait / waitpid),那么它就会成为一个僵尸进程。

16.2僵死进程的危害:

机制:子进程运行结束退出时,内核会释放该进程的所有资源,包括打开的文件,占用的内存等。但是仍会保留一些信息(包括进程号,退出状态,运行时间等),直到父进程通过wait()或者waitpid()获取时才会释放。

如果父进程一直不调用wait()或者waitpid(),其进程号会被一直占用。

其危害就在于系统能用的进程号是有限的,如果产生大量僵尸进程,则会导致没有可使用的进程号而导致系统不能产生新的进程

16.3解决僵尸进程问题:

1.父进程通过wait或waitpid等函数等待子进程结束,这回导致父进程挂起。

2.如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。

3.如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对子进程不感兴趣,那么子进程结束后,内核会回收,并不会给父进程发送信号。

4.fork()两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。

子进程运行结束,父进程获取子进程的退出码。即父进程通过wait()或者waitpid()完成

16.4孤儿进程概念:

父进程先于子进程结束,子进程的父进程由init进程接管,成为子进程的父进程

若该进程的父进程先结束,那么该进程就不会变为僵尸进程。因为每个进程结束时,系统都会扫描当前系统中所运行的所有进程,看是否还存在该进程的子进程未结束,如果存在,那么就由init接管成为该子进程的父进程。

16.5wait()函数

wait一直阻塞,直到子进程运行结束,获取子进程的退出码后,继续当前父进程后续执行

wait()函数的返回值为子进程的PID,传参为整型指针变量,用于存储包含退出码的数据。

WIFEXITED()判断获取的数据中是否存在退出码,返回值为bool类型,参数为包含退出码的数据。

WEXITSTATUS()获取数据中的退出码,参数为包含退出码的数据。

#include
#include
#include
#include
int mian()
{
    int n = 0;
    char* s = NULL;
    pid_t pid = fork();
    if(pid==0)
    {
        n=3;
        s="son";
    }
    else
    {
        n=7;
        s="parent";
        int val;//4字节   获取子进程的退出码
        pid_t chile_pid=wait(&val);//
        if(WIFEXITED(val))
        {
            printf("wait的返回值%d    退出码为%d\n",chile_pid,WEXITSTATUS(val));//此处打印退出码
        } 
    }
    for(int i=0;i

十七、进程的替换

17.1概念:

把当前进程替换为其他进程执行。替换的是进程的实体

17.2进程替换使用的是exec系列的函数

头文件为 #incldue

execl execlp execle -> l 把参数列举出来

execv execvp execve -> v 参数封装到数组里

前五个是库函数,底层调用为 execve系统调用;execve是系统调用函数

p -> PATH(路径) e -> envp(环境变量)

17.3 execl

which ps -f 查找 ps -f 命令的位置

execl(命令位置,命令,该命令的参数(若没有参数就不需要写,若有几个参数继续往后加就可以),字符串结束标记((char*)0)))

execl("/usr/bin/ps","ps","-f",(char*)NULL);

在上面语句执行成功后就不会再执行其后面的代码,只有该语句执行失败,才会执行后面的代码。

perror("exec error:");作用 -> 在执行此语句后会打印错误的原因,可以用来替换printf函数

ls使用的是ls --color,所以使用ls后会有颜色,而进程替换为ls没有颜色

命令后带有一个 - 代表将 - 之后每个字符单独解析含义

命令后带有一个 -- 代表将 -- 之后所有字符一起作为单词解析含义

17.4 进程替换实现

#include
#include
#include
#include
int main()
{
    pid_t pid = fork();
    if(pid==0)
    {
        //execl("usr/bin/ls","ls","--color","-a",(char*)0);
        execl("./main","./main",(char*)0);
        perror("exec error");//能运行到当前位置,说明替换失败
    }
    else
    {
        wait(0);//获取子进程的退出码 阻塞函数 等待子进程的结束,子进程结束,才会执行父进程 
        sleep(5);
        printf("parent run .....\n");
    }
    exit(0);
}

17.5   l  系列

使用execl函数

execlp(命令位置,命令,该命令的参数(若没有参数就不需要写,若有几个参数继续往后加就可以),字符串结束标记((char*)0)))

execlp("usr/bin/ps","ps","-f",(char*)0);

使用execlp函数

execlp(命令(如果是系统命令不需要具体位置,只需要给出命令名字,默认PATH为 /usr/bin路径下),命令,该命令的参数(若没有参数就不需要写,若有几个参数继续往后加就可以),字符串结束标记((char*)0)))

execlp("ps","ps","-f",(char*)0);

如果使用用户自定义路径,则需要给系统变量PATH路径后添加自定义路径即为:

export 变量名=值 -> export PATH=$PATH : . 在系统变量PATH内容后添加用户自定义的路径

execlp("main","./main",(char*)0);

添加后需要删除,和添加相同,export PATH=未改变前的路径

使用echo $PATH可进行查看

使用execle函数

execle(命令位置,命令,该命令的参数(若没有参数就不需要写,若有几个参数继续往后加就可以),字符串结束标记((char*)0)),envp(使用环境变量才使用此函数))

execle("usr/bin/pwd","pwd",(char*)0,envp);

17.6   v  系列

使用execv函数

execv(命令位置,字符指针数组名(该数组中存在需要罗列的字符串))

char* buff[]={"ps","-f",(char*)0};
execle("usr/bin/ps",buff);

使用execvp函数

execvp(命令(如果是系统命令不需要具体位置,只需要给出命令名字,默认PATH为 /usr/bin路径下。如果是自定义路径则需要进行 添加),字符指针数组名(该数组中存在需要罗列的字符串))

char* buff[]={"ps","-f",(char*)0};
execvp("ps",buff);

使用execve函数

execve(命令位置,字符指针数组名(该数组中存在需要罗列的字符串),envp)

char* buff[]={"ps","-f",(char*)0};
execle("usr/bin/ps",buff,envp);

l系列和v系列区别在于前者罗列出来,后者封装到数组中

你可能感兴趣的:(linux,运维,服务器)