文章目录

Linux下进程相关操作1. 进程概念1.1 进程基本概念1.2 Linux下PCB的定义2.进程查看3. 进程创建3.1 fork函数3.2 vfork函数3.3 fork与vfork的异同3.4 写时拷贝技术4. 进程退出4.1 exit函数4.2 _exit函数4.3 exit与_exit的异同5. 进程等待5.1 wait函数5.2 waitpid函数6. 程序替换6.1 execl函数和execv函数6.2 execlp函数和execvp函数6.3 execle函数和execve函数

Linux下进程相关操作

  1. 进程概念
    1.1 进程基本概念

    进程定义:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。

    简言之:进程是程序的一次执行过程

    进程特性:

     动态性:进程是动态产生的,往往都会经历创建、运行、消亡三个状态独立性:各个进程之间的地址空间相互独立并发性:任何进程都可以一起向前推进异步性:每个进程都以其不可预知的速度向前推进结构化:进程 = 代码段 + 数据段 + PCB(进程控制块)
    

    进程与程序的区别:

     进程是动态的执行过程;程序是静态的代码进程是暂时的,运行在内存中的一个状态变化的过程;程序是永久的,保存在外存中通过多次执行一个程序,进而产生多个进程,所以一个程序可以对应多个进程;进程通过调用多个程序,进而一个进程可以执行多个程序,所以一个进程可以对应多个程序
    

进程是竞争计算机资源的基本单位
1.2 Linux下PCB的定义

进程控制块(PCB)是用来记录进程相关信息和管理进程而设置的一种数据结构进程控制块(PCB)是由操作系统(OS)维护的系统通过PCB感知进程的存在PCB随进程的创建而创建并填写,随着进程的消亡而释放PCB的组成:进程标识符:用于唯一标识该进程的整数进程名:通常是可执行文件名优先级:进程优先运行的权重寄存器值:用于保存当前进程运行到某一时刻各种数据信息…

在Linux操作系统中,每一个进程都有一个PCB,每一个PCB都对应一个task_struct结构体,简言之,每创建一个进程就相当于创建一个task_struct结构体并填写其中的数据。

Linux中的PCB定义在sched.h文件中若想获得sched.h文件,需要到Linux内核官网下载内核源码以Linux Kernel Source Code 2.6.32 为例展示task_struct部分源码

在这里插入图片描述
2.进程查看

使用ps命令可以查看正在运行的进程瞬时的信息,而不是动态连续的信息参数:-a : 显示所有用户的进程-u : 以用户为主的进程状态-x : 列出较完整的信息打印显示出来的信息:PID:进程标识符TTY:命令所运行的位置STAT:进程状态TIME:运行该命令所占用的CPU处理时间COMMAND:该进程所运行的命令1.显示当前所有进程及详细信息

[gongruiyang@localhost ~]$ ps -ax
PID TTY STAT TIME COMMAND
1 ? Ss 0:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
2 ? S 0:00 [kthreadd]
3 ? S 0:00 [ksoftirqd/0]
5 ? S< 0:00 [kworker/0:0H]
7 ? S 0:00 [migration/0]

1
2
3
4
5
6
7
82.显示某用户的进程及详细信息

[gongruiyang@localhost ~]$ ps -u gongruiyang
PID TTY TIME CMD
1736 ? 00:00:00 gnome-keyring-d
1741 ? 00:00:00 gnome-session-b
1748 ? 00:00:00 dbus-launch
1749 ? 00:00:00 dbus-daemon
1807 ? 00:00:00 gvfsd
1812 ? 00:00:00 gvfsd-fuse
1904 ? 00:00:00 ssh-agent
1923 ? 00:00:00 at-spi-bus-laun
1928 ? 00:00:00 dbus-daemon

1
2
3
4
5
6
7
8
9
10
11
123.显示进程及其CPU和内存占用情况

[gongruiyang@localhost ~]$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 128212 6836 ? Ss 11:00 0:01 /usr/li
root 2 0.0 0.0 0 0 ? S 11:00 0:00 [kthrea
root 3 0.0 0.0 0 0 ? S 11:00 0:00 [ksofti
root 5 0.0 0.0 0 0 ? S< 11:00 0:00 [kworke
root 7 0.0 0.0 0 0 ? S 11:00 0:00 [migrat
root 8 0.0 0.0 0 0 ? S 11:00 0:00 [rcu_bh

1
2
3
4
5
6
7
8
  1. 进程创建
    3.1 fork函数

pid_t fork(void)

1fork调用一次,会在父进程中返回一个值,会在子进程中返回一个值父进程中:返回子进程PID子进程中:返回 0Q:为什么fork会返回两次呢?A:子进程复制父进程的堆和栈中的内容,此时,两个进程都处于fork函数中,都在等待fork函数执行结束并返回一个pid_t,所以会有两个返回值pid_t实际上就是int,被定义在sys/typpes.h中

代码演示:

getpid()函数是获得当前进程的进程号getppid()函数是获得当前进程父进程的进程号

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

int main()
{
printf(" [fpid] [pid] [ppid]\n");
pid_t fpid = fork();

if(fpid < 0) //创建进程失败
perror(“fork”);
else if(fpid == 0)//子进程执行这一段代码
printf(“child: %4d %4d %4d\n”,fpid,getpid(),getppid());
else //父进程执行这一段代码
printf(“father: %4d %4d %4d\n”,fpid,getpid(),getppid());

while(1) //父子进程都执行
{
sleep(1);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

运行结果:

   [fpid] [pid] [ppid]

father: 5081 5080 2717
child: 0 5081 5080

1
2
3解释:pid为5080的父进程 创建了一个 pid为5081的子进程,其中 父进程的 父进程号为2717,子进程的 父进程号为5080fork特性:子进程将父进程中打开的所有文件描述符都复制了一遍,父子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用数量增加了子进程不会继承父进程的一些数据:子进程不复制父进程设置的锁(若继承会导致排它锁矛盾)子进程不复制父进程的pid,而是产生自己的pid子进程不复制父进程中的pending alarms和pending signals,而是将自己的pending alarms清除,将pending signals置为空fork产生失败可能的两个原因:当前运行中的进程数已经达到了系统规定的上限,此时错误码(errno)的值为EAGAIN当前系统内存容量不足以开辟一个新的进程,此时错误码(errno)的值为ENOMEM

3.2 vfork函数

pid_t vfork(void)

1该函数返回值特点与fork相同vfork创建出来的子进程并不会直接将父进程的虚拟空间内容拷贝一份,而是与父进程共享一份虚拟空间,当子进程需要修改数据时,才会进行拷贝,这称之为写时拷贝技术父进程使用vfork创建子进程后,父进程会被挂起,直到子进程终止或被替换后,才能继续推进应当使用exit或_exit来终止vfork创建的子线程,不能使用return来终止。若使用return来终止子线程会导父进程回到调用vfork处,进而无限创建子进程进而产生段错误

代码演示:

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

int main()
{

pid_t fpid = vfork();

if(fpid < 0)
perror(“vfork”);
else if(fpid == 0)
{
printf(“child:%d\n”,getpid());
while(1)
{
printf(“while\n”);
sleep(1);
}
}
else
printf(“father:%d\n”,getpid());

printf("========================\n");

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

输出结果:

child:5898
while
while
while
while
while
^C

1
2
3
4
5
6
7解释:由于子进程处于循环sleep中,导致父进程一直被挂起无法执行下面的代码

3.3 fork与vfork的异同

异:

fork创建的子进程是父进程的一个副本,子进程将父进程的堆栈中存的数据信息拷贝一份到另外开辟的内存中去,并不能共享这些数据;vfork创建的子进程并不会立刻开辟新内存拷贝数据,而是共享父进程的堆栈中的数据,直到子进程终止或者被替换之前,都是在父进程的空间中运行
vfork保证子进程先运行;fork是让两个进程异步运行

同:

fork和vfork都是调用一次,但是返回两次

3.4 写时拷贝技术

传统拷贝方法:为子进程的页表分配页帧为子进程的页分配页帧初始化子进程的页表把父进程的页内容复制到子进程对应页中传统的拷贝父进程资源实现过于简单且效率低下
写时拷贝是一种可以推迟甚至免除拷贝数据的技术写时拷贝(Copy - On - Write)技术:子进程并不拷贝父进程的数据资源,而是父子进程共享父进程原有的数据资源,只有当要写入的时候,才进行资源的复制

实际上,COW不但在Linux进程上有实际应用,而且在C++的String类在g++环境下也支持COW技术

#include
#include <stdio.h>
#include
using namespace std;

int main()
{
string str1 = “Hello\n”;
string str2 = str1;

printf("Befor Write: str1[%x] str2[%x]\n", str1.c_str(), str2.c_str());str2.clear();
str2 = "H\n";
printf("After Write: str1[%x] str2[%x]\n", str1.c_str(), str2.c_str());return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输出结果:

[gongruiyang@localhost TestCOW]$ g++ COWtest.cpp -o test
[gongruiyang@localhost TestCOW]$ ./test
Befor Write: str1[18cec38] str2[18cec38]
After Write: str1[18cec38] str2[18cec98]

1
2
3
4解释:从输出结果来看,str1和str2一开始指向同一内存地址,共享Hello这个数据,当str2要进行写入的时候,str2指向的地址发生改变,重新开辟空间存放数据,即只有写入东西的时候才进行内存的再分配
  1. 进程退出
    4.1 exit函数

void exit(int status)

1功能:正常终止进程
头文件:stdlib.h
status:返回给父进程的状态值,通常用0或EXIT_SUCCESS表示成功,通常用非0或EXIT_FAILURE表示异常程序终止

代码测试:

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

int main()
{
pid_t fpid = fork();
int status;
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
printf(“ChildP: childPID[%d]\n”,getpid());
exit(EXIT_SUCCESS); //正常退出子进程
}
else
{
int ret_pid = wait(&status); //父进程阻塞等待子进程退出
printf(“FatherP: %d is normally exited with status:%d\n”,ret_pid,status);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

输出结果:

ChildP: childPID[8071]
FatherP: 8071 is normally exited with status:0

1
2

4.2 _exit函数

void _exit(int status);

1功能:立即终止子进程,并关闭所有属于该进程的文件描述符,该进程的所有子进程过继给init进程,并向父进程发送SIGCHLD信号,将status作为子进程退出状态返回给父进程
头文件:unistd.h
status:返回给父进程的状态值,通常用0或EXIT_SUCCESS表示成功,通常用非0或EXIT_FAILURE表示异常程序终止

代码测试:

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

int main()
{
pid_t fpid = fork();
int status;
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
printf(“ChildP: childPID[%d]\n”,getpid());
_exit(EXIT_SUCCESS);
}
else
{
int ret_pid = wait(&status);
printf(“FatherP: %d is normally exited with status:%d\n”,ret_pid,status);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

输出结果:

ChildP: childPID[8586]
FatherP: 8586 is normally exited with status:0

1
2

4.3 exit与_exit的异同

异:

exit定义在stdlib.h中;_exit定义在unistd.h中
exit会进行缓冲区刷新;_exit不会刷新缓冲区可能导致数据丢失

同:

退出子进程并向父进程返回退出状态信息
exit是_exit的封装形式

代码测试:

#include <stdio.h>
#include <stdlib.h>

int main()
{
printf(“Hello!\n”);
printf(“World!”);
exit(0);

return 0;
}

1
2
3
4
5
6
7
8
9
10
11

#include <stdio.h>
#include <unistd.h>
int main()
{
printf(“Hello!\n”);
printf(“World!”);
_exit(0);

return 0;
}

1
2
3
4
5
6
7
8
9
10

exit输出结果:

Hello!
World!

1
2

_exit输出结果:

Hello!

1程序解释:由测试代码可以看出_exit由于不刷新缓冲区导致了数据丢失,而exit将__exit进行封装了,便得到更加安全,保全了数据
  1. 进程等待

等待子进程退出->获取子进程返回值->释放子进程资源->防止僵尸进程产生->防止资源泄漏
5.1 wait函数

pid_t wait (int* status)

1包含头文件:sys/types.h和sys/wait.h
status:出参,用于保存子进程退出时的状态。正常退出情况下,一个int中的低16位中的高8位保存返回值(取出返回值:status & 0x7f )异常退出情况下,一个Int中的低7位保存异常退出信号值(取出异常信号值:(status >> 8) & 0xff)
返回值:退出进程的pid功能:阻塞父进程一直等待子进程的退出,当子进程退出后,父进程才脱离阻塞继续推进

WIFEXITED(int status)

1

该宏是用于检测进程是否正在退出:

宏值若为0代表非正常退出,若为非0则代表正常退出

代码演示:

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

int main()
{
pid_t fpid = fork();
int status = 0;

if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
printf(“Child:[%d]\n”,getpid());
exit(3);
}
else
{
printf(“Father:[%d]\n”,getpid());
pid_t ret_pid = wait(&status);
if(WIFEXITED(status))
printf("%d has exited with return code %d\n",ret_pid,WEXITSTATUS(status));
else
perror(“wait”);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

输出结果:

Father:[6985]
Child:[6986]
6986 has exited with return code 3

1
2
3

5.2 waitpid函数

pid_t waitpid(pid_t pid, int* status, int options)

1

参数:

status:出参,用于保存子进程的退出状态
pid

0 只等待进程ID为指定pid的子进程
= -1 等待任何一个子进程,与wait功能一样
= 0 等待任一个【子进程组ID=父进程组ID】的子进程
< -1 等待任一个【子进程组ID= |父进程组ID|】的子进程 (||意为绝对值)

options

WNOHANG 指定子进程结束才阻塞父进程,返回值为0
0 与wait相同,阻塞父进程,一直等待子进程退出
WUNTRACED 子进程若处于暂停状态:立刻返回
子进程若处于结束状态:不予理会

返回值

0 已经结束的子进程的pid
0 使用options为WUNTRACED且无子进程退出
-1 调用出错(例如:无子进程)

功能:非阻塞父进程版本wait

wait中其实调用了waitpid

代码测试:

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

int main()
{
pid_t fpid = fork();
pid_t ret_waitpid = 0;
int status;

if(fpid < 0)
perror(“fork”);
else if(fpid == 0) //子进程
{
printf(“Child:%d\n”,getpid());
sleep(3);
exit(0); //子进程退出
}
else //父进程
{
while(!ret_waitpid) //循环waitpid等待子进程的退出
{
ret_waitpid = waitpid(fpid,&status,WNOHANG);
if(ret_waitpid == 0)
{
printf("%d has not exit\n",fpid); //子进程未退出就打印
sleep(1);
}
}//while结束:说明此事ret_waitpid接收到了子进程exit后返回的pid
if(ret_waitpid == fpid)
printf("%d has exited with return code:%d\n",fpid,status);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

输出结果:

9311 has not exit
Child:9311
9311 has not exit
9311 has not exit
9311 has exited with return code:0

1
2
3
4
5
  1. 程序替换

    创建子进程的目的是为了完成其他的事情,完成其他任务,这个时候就用到了程序替换

辅助代码:子进程需要做的任务,可执行文件名为child_task,绝对路径为:/home/gongruiyang/ClassLinunx/pidTest/child_task

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

void child_task(int argc, char* argv[])
{
for(int i = 0; i < argc; i++)
printf(“argv[%d] = %s\n”,i,argv[i]);
}
//main函数有俩参数:argc表示程序的运行参数个数,argv用于保存程序参数
int main(int argc,char* argv[])
{
child_task(argc,argv );

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

6.1 execl函数和execv函数

int execl(const char *path, const char *arg, …);

1path:用于保存想要执行的可执行文件绝对路径
arg:执行可执行文件所需要的参数,如果不需要参数可以填NULL
…:不定参数,说明后面的参数可以是1个,也可以是多个

代码测试:

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

int main()
{
pid_t fpid = fork();

if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
execl("/home/gongruiyang/ClassLinunx/pidTest/child_task","-a","-b");
}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

输出结果:

[gongruiyang@localhost pidTest]$ ./exectest
Father Do!
argv[0] = -a
argv[1] = -b

1
2
3
4

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

1path:用于保存想要执行的可执行文件绝对路径
argv:参数列表

代码测试:

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

int main()
{
pid_t fpid = fork();
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{

char* child_argv[32];
child_argv[0] = "-a";
child_argv[1] = "-b";
child_argv[2] = "-c";
child_argv[3] = NULL; //必须要有execv("/home/gongruiyang/ClassLinunx/pidTest/child_task",child_argv)

}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

输出结果:

Father Do!
argv[0] = -a
argv[1] = -b
argv[2] = -c

1
2
3
4

6.2 execlp函数和execvp函数

加了p之后,其中path可以不填绝对路径,该函数会去PATH环境变量中寻找

int execlp(const char *path, const char *arg, …);

1path:用于保存想要执行的可执行文件路径
arg:执行可执行文件所需要的参数,如果不需要参数可以填NULL

代码测试:

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

int main()
{
pid_t fpid = fork();
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
execlp(“ls”,“ls”,"-l",NULL); //在环境变量中寻找ls并执行
}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

输出结果:

Father Do!
总用量 144
-rwxrwxr-x. 1 gongruiyang gongruiyang 8561 12月 30 17:33 child_task
-rw-rw-r–. 1 gongruiyang gongruiyang 348 12月 30 17:34 child_task.c

1
2
3
4
5

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

1path:用于保存想要执行的可执行文件路径
argv:参数列表

测试代码:

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

int main()
{
pid_t fpid = fork();
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
char* child_argv[10];
child_argv[0] = “ls”;
child_argv[1] = “-l”;
child_argv[2] = NULL;
execvp(“ls”,child_argv);
}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

结果输出:

Father Do!
总用量 144
-rwxrwxr-x. 1 gongruiyang gongruiyang 8561 12月 30 17:33 child_task
-rw-rw-r–. 1 gongruiyang gongruiyang 348 12月 30 17:34 child_task.c

1
2
3
4

6.3 execle函数和execve函数

辅助程序:打印环境变量

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char* argv[],char* env[])
{
printf(“env-var:”);
for(int i = 0 ; env[i] != NULL; i++)
printf(“env[%d]:%s\n”,i,env[i]);
return 0;
}

1
2
3
4
5
6
7
8
9
10

int execle(const char *path, const char *arg,…, char * const envp[]);

1path:用于保存想要执行的可执行文件绝对路径
arg:执行可执行文件所需要的参数,如果不需要参数可以填NULL
…:不定参数,说明后面的参数可以是1个,也可以是多个
envp:自定义环境变量,可以填NULL

int execvpe(const char *path, char *const argv[], char *const envp[]);

1path:用于保存想要执行的可执行文件路径
argv:参数列表
envp:自定义环境变量,可以填NULL

加了e之后,相比于之前,增加了一个环境变量参数,可以使用自定义环境变量

测试代码:

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

int main()
{
pid_t fpid = fork();
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
char* child_env[10];
child_env[0] = “MYVAL=1000”;
child_env[1] = “TMP=12”;
child_env[2] = NULL;
execvpe("/home/gongruiyang/ClassLinunx/pidTest/envtest",NULL,child_env);
//execvle("/home/gongruiyang/ClassLinunx/pidTest/envtest",NULL,child_env);
}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

输出结果:

Father Do!
env-var:env[0]:MYVAL=1000
env[1]:TMP=12

1
2
3

用于保存想要执行的可执行文件绝对路径

arg:执行可执行文件所需要的参数,如果不需要参数可以填NULL
…:不定参数,说明后面的参数可以是1个,也可以是多个
envp:自定义环境变量,可以填NULL

int execvpe(const char *path, char *const argv[], char *const envp[]);

1path:用于保存想要执行的可执行文件路径
argv:参数列表
envp:自定义环境变量,可以填NULL

加了e之后,相比于之前,增加了一个环境变量参数,可以使用自定义环境变量

测试代码:

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

int main()
{
pid_t fpid = fork();
if(fpid < 0)
perror(“fork”);
else if(fpid == 0)
{
char* child_env[10];
child_env[0] = “MYVAL=1000”;
child_env[1] = “TMP=12”;
child_env[2] = NULL;
execvpe("/home/gongruiyang/ClassLinunx/pidTest/envtest",NULL,child_env);
//execvle("/home/gongruiyang/ClassLinunx/pidTest/envtest",NULL,child_env);
}
else
{
printf(“Father Do!\n”);
sleep(1);
}

sleep(1);
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

输出结果:

Father Do!
env-var:env[0]:MYVAL=1000
env[1]:TMP=12

【Linux】一篇文章搞定 进程 及相关操作相关推荐

  1. 一篇文章搞定GVIM(根据工作经验持续更新)

    文章目录 0.引言 1.在Linux下面安装VIM 2.基本操作 2.1三种模式 2.1 保存退出:wq没反应?! 2.2 解决鼠标不能用的问题 2.3 VIM上下左右移动hjkl 2.4 跳转到第n ...

  2. 超硬核!!!一篇文章搞定TCP、UDP、Socket、HTTP(详细网络编程内容+现实解释三次握手四次挥手+代码示例)【网络编程 1】

    TCP.UDP.Socket 一天面试的经验: 什么是网络编程 网络编程中两个主要的问题 网络协议是什么 为什么要对网络协议分层 计算机网络体系结构 1 TCP / UDP 1.1 什么是TCP/IP ...

  3. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  4. python基础知识-一篇文章搞定Python全部基础知识

    原标题:一篇文章搞定Python全部基础知识 前言: 1.Python软件安装 第一章.字符串及数字变量 1.变量 要点提炼:Python变量为强类型动态类型.换言之,变量很任性,你给他int,他就是 ...

  5. 一篇文章搞定DX9.0c 环境里的3DXSpriet !!

    四年前写过一篇<一篇文章搞定3DXSpriet !! >得到了很多的反馈,现在那篇文章中的好多代码已经不可以再用了,所以对其中的一些代码做了改动,为了方便初学者掌握3DXSpriet,再写 ...

  6. 一篇文章搞定百度OCR图片文字识别API

    一篇文章搞定百度OCR图片文字识别API https://www.jianshu.com/p/7905d3b12104 转载于:https://www.cnblogs.com/chongdongxia ...

  7. 一篇文章搞定《RecyclerView缓存复用机制》

    一篇文章搞定<RecyclerView缓存复用机制> 前言 零.为什么要缓存 一.RecyclerView如何构建我们的列表视图 二.缓存过程 三.缓存结构 1.mChangedScrap ...

  8. 一篇文章搞定java中的垃圾回收机制面试题

    一篇文章搞定java中的垃圾回收机制面试题 任何语言在运行过程中都会创建对象,也就意味着需要在内存中为这些对象在内存中分配空间,在这些对象失去使用的意义的时候,需要释放掉这些内容,保证内存能够提供给新 ...

  9. 一篇文章搞定《Android布局优化》

    ------<一篇文章搞定Android布局优化> 前言 为什么要进行布局优化? Android绘制原理 双缓冲机制 布局加载原理 布局加载优化的一些方法介绍 AsyncLayoutInf ...

最新文章

  1. 2015 年 Git@OSC 新增热门开源软件项目 Top 50
  2. Python基础第27天
  3. 彩色图如何转换成灰度
  4. Python教程:通过函数名调用函数的3种场景实现
  5. python dataframe loc函数_python pandas.DataFrame.loc函数使用详解
  6. 批量关闭公众号推送_啥?微信又出新功能了?或将提醒用户关闭长期未读公众号推送...
  7. 速查100 WebServers
  8. SAP Hybris的build callback和SAP ABAP的SGEN事务码
  9. 在Oracle里,表的别名不能用as,列的别名可以用as
  10. wdatepicker不展示秒_十一出游去哪里?苏州人都不一定知道的地方!
  11. windows2003与文件共享有关的几个进程
  12. golang中值类型/指针类型的变量区别总结
  13. java贪心算法几个经典例子_经典算法思想5——贪心(greedy algorithm)
  14. NeHe OpenGL教程(中文版附源代码)
  15. 机器学习基础算法13-回归实例-时间序列分析ARIMA
  16. MyBatis简单了解
  17. CSS的BEM命名规范
  18. Soap+xml实现webservice 调用
  19. jacket for matlab,Jacket for Matlab常见问题
  20. 10款必装软件,让Windows使用效率飞起!

热门文章

  1. 服务编排:conductor学习(一)
  2. Winpcap网络编程九之Winpcap实战,ARP协议获得MAC表及主机通信
  3. 探讨一下论文查重系统的设计方法
  4. Core-periphery decomposition--核心-外围模型R代码整理
  5. 导入idea中运行项目 如何解决各种程序包不存在的问题
  6. Blob分析---check_hazelnut_wafers.hdev(检测榛子饼干的质量)
  7. 日常英语单词 - 动物
  8. 利用Python轻松实现图片相似度对比(一)
  9. 黑盒测试详细功能描述
  10. c/c++中int,long,long long的取值范围