Linux—进程产生的方式(进程号、fork函数、system函数、exec函数、init进程)

目录

1. 进程号

1.1 getpid()、getppid()函数介绍

1.2 getpid()函数的例子

2. 进程复制fork()

2.1 fork()函数介绍

2.2 fork()函数的例子

3. system()方式

3.1 system()函数介绍

3.2 system()函数的例子

4. 进程执行exec()函数系列

4.1 exec()函数介绍

4.2 execve()函数的例子

5. 所有用户进程的产生进程init


进程是计算机中运行的基本单位,要产生一个进程,有多种产生方式,例如使用fork()函数、system()函数、exec()函数等,这些函数的不同在于其运行环境的构造之间存在差别,其本质都是对程序运行的各种条件进行设置,在系统之间建立一个可以运行的程序,

1. 进程号

每个进程在初始化的时候,系统都分配了一个ID号,用于标识此进程在Linux中进程号是唯一的,系统可以用这个值来表示一个进程,描述进程的ID号通常叫做PID,即进程ID(process id)。PID的变量类型为pid_t。

1.1 getpid()、getppid()函数介绍

getpid()函数返回当前进程的ID号,getppid()返回当前进程的父进程的ID号类型pid_t其实是一个typedef类型,定义为unsigned int。getpid()函数和getppid()函数的原型如下:

#include <sys/types.h>

#include <unistd.h>

pid_t getpid(void);

pid_t getppid(void);

1.2 getpid()函数的例子

下面是一个使用getpid()函数和getppid()函数的例子。程序获取当前程序的PID和父程7序的PID。

#iuclude <sys/types.h>

#include <unistd.h>

#include<stdio.h>

int main()

{

pid_t pid,ppid;


/*获得当前进程和其父进程的ID号*/

pid = getpid();

ppid = getppid();


printf (“当前进程的 ID号为:%d\n”, pid);

printf (“当前进程的的父进程号ID号为:%d\n”, ppid);


return 0;

}

对上述程序进行编译,在系统上进行运行,其结果为:

可以知道,进程的ID号为2541,其父进程的ID号为2319。在当前系统上使用ps和 grep进行进程2319的查找,可以知道,ID号为2319的进程为bash,即当前环境中的脚本程序。查找其父进程的命令:

因为是在当前bash中运行的此程序,所以ID 2541其父进程为bash。

2. 进程复制fork()

产生进程的方式比较多,fork()是其中的一种方式。fork()函数以父进程为蓝本复制一个进程,其ID号和父进程ID号不同。在Linux环境下,fork()是以写复制实现的只有内存等与父进程不同,其他与父进程共享,只有在父进程或者子进程进行了修改后,才重新生成一份。

2.1 fork()函数介绍

fork()函数的原型如下,当成功时,fork()函数的返回值是进程的ID;失败则返回-1。

#linclude <sys/types.h>

#include <unistd.h>

pid_t fork(void);

fork()的特点是执行一次,返回两次。在父进程和子进程中返回的是不同的值,父进程返回的是子进程的ID号,而子进程中则返回0。

2.2 fork()函数的例子

下面是一个使用fork()函数的例子。在调用fork()函数之后,判断fork()函数的返回值: 如果为-1,打印失败信息;如果为0,打印子进程信息;如果大于0,打印父进程信息。

执行此段程序的结果为:

fork出来的子进程的父进程ID号是执行fork()函数的进程的ID号。

3. system()方式

system()函数调用shell的外部命令在当前进程中开始另一个进程

3.1 system()函数介绍

system()函数调用“/bin/sh-c command”执行特定的命令,阻塞当前进程直到command命令执行完毕。system()函数的原型如下:

#linclude <stdlib.h>

int system (const char *command);

执行system()函数时,会调用fork()、execve()、waitpid()等函数,其中任意一个调用失败,将导致system()函数调用失败。system()函数的返回值如下:

□ 失败,返回-1;

□ 当sh不能执行时,返回127;

□ 成功,返回进程状态值。

当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的 pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl("/bin/sh", "sh", "-c", cmdstring,(char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个 shell进程,这个shell的参数是cmdstring,就是system接受的参数。

system(执行shell 命令)

相关函数

forkexecvewaitpidpopen

表头文件

#include<stdlib.h>

定义函数

int system(const char * string);

函数说明

system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINTSIGQUIT 信号则会被忽略。

返回值

如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。如果 system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。

附加说明

在编写具有SUID/SGID权限的程序时请勿使用system()system()会继承环境变量,通过环境变量可能会造成系统安全的问题。

3.2 system()函数的例子

例如下面的代码获得当前进程的ID,并使用system()函数进行系统调用ping网络上的某个主机,程序中将当前系统分配的PID值和进行system()函数调用的返回值都进行了打印:

对上述代码进行编译,执行编译后的结果,其执行结果为:

系统分配给当前进程的ID号为17068:然后系统ping 了网络上的某个主机,发送和 接收两个ping的请求包,再退出ping程序;此时系统的返回值在原来的程序中才返回,在测试的时候返回的是0。

4. 进程执行exec()函数系列

在使用fork()函数和system()函数的时候,系统中都会建立一个新的进程,执行调用者的操作,而原来的进程还会存在,直到用户显式地退出;而exec()族的函数与之前的fork()和system()函数不同,exec()族函数会用新进程代替原有的进程,系统会从新的进程运行,新进程的PID值会与原来进程的PID值相同。

4.1 exec()函数介绍

exec()族函数共有7个,其原型如下:

#include <unistd.h>

extern char **environ;

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

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

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

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

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

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

int execve(const char *filename, char *const argv[ ], char *const envp[ ]);

上述7个函数中,只有execve()函数是真正意义上的系统调用,其他6个函数都是在此基础上经过包装的库函数。上述的exec()函数族的作用是,在当前系统的可执行路径中根据指定的文件名来找到合适的可执行文件名,并用它来取代调用进程的内容,即在原来的进程内部运行一个可执行文件。上述的可执行文件既可以是二进制的文件,也可以是可执行的脚本文件。

与fork()函数不同,exec()函数族的函数执行成功后不会返回,这是因为执行的新程序已经占用了当前进程的空间和资源,这些资源包括代码段、数据段和堆栈等,它们都己经被新的内容取代,而进程的ID等标识性的信息仍然是原来的东西,即exec()函数族在原来进程的壳上运行了自己的程序,只有程序调用失畋了,系统才会返回-1

使用exec()函数比较普遍的一种方法是先使用fork()函数分叉进程,然后在新的进程中调用exec()函数,这样exec()函数会占用与原来一样的系统资源来运行。

Linux系统针对上述过程专门进行了优化。由于fork()的过程是对原有系统进行复制,然后建立子进程,这些过程都比较耗费时间。如果在fork()系统调用之后进行exec()系统调用,系统就不会进行系统复制,而是直接使用exec()指定的参数来覆盖原有的进程。上述的方法在Linux系统上叫做“写时复制”,即只有在造成系统的内容发生更改的时候才进行进程的真正更新

4.2 execve()函数的例子

execve()函数的例子如下。示例程序中先打印调用进程的进程号,然后调用execve()函数,这个函数调用可执行文件”/bin/ls”列出当前目录下的文件。

执行结果如下所示:

 

5. 所有用户进程的产生进程init

在Linux系统中,所有的进程都是有父子或者堂兄关系的,除了初始进程init,没有哪个进程与其他进程完全独立。系统中每个进程都有个父进程,新的进程不是被全新地创建,通常是从一个原有的进程进行复制或者克隆的

Linux操作系统下的每一个进程都有一个父进程或者兄弟进程,并且有自己的子进程。可以在Linux下使用命令pstree来查看系统中运行的进程之间的关系,如下所示。可以看出,init进程是所有进程的祖先,其他的进程都是由init进程直接或者间接fork()出来的

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 封面 -12 封底 -11 扉页 -10 版权 -9 版权声明 -8 前言 -7 目录 -3 第一部分 简介 1 第1章 简介 2 1.1 概述 2 1.2 进程、线程与信息共享 3 1.3 IPC对象的持续性 4 1.4 名字空间 5 1.5 forkexec和exit对IPC对象的影响 7 1.6 出错处理:包裹函数 8 1.7 Unix标准 9 1.8 书中IPC例子索引表 11 1.9 小结 13 习题 13 第2章 Posix IPC 14 2.1 概述 14 2.2 IPC名字 14 2.3 创建与打开IPC通道 16 2.4 IPC权限 18 2.5 小结 19 习题 19 第3章 System V IPC 20 3.1 概述 20 3.2 key_t键和ftok函数 20 3.3 ipc_perm结构 22 3.4 创建与打开IPC通道 22 3.5 IPC权限 24 3.6 标识符重用 25 3.7 ipcs和ipcrm程序 27 3.8 内核限制 27 3.9 小结 28 习题 29 第二部分 消息传递 31 第4章 管道和FIFO 32 4.1 概述 32 4.2 一个简单的客户-服务器例子 32 4.3 管道 32 4.4 全双工管道 37 4.5 popen和pclose函数 39 4.6 FIFO 40 4.7 管道和FIFO的额外属性 44 4.8 单个服务器,多个客户 46 4.9 对比迭代服务器与并发服务器 50 4.10 字节流与消息 51 4.11 管道和FIFO限制 55 4.12 小结 56 习题 57 第5章 Posix消息队列 58 5.1 概述 58 5.2 mq_open、mq_close和mq_unlink函数 59 5.3 mq_getattr和mq_setattr函数 61 5.4 mq_send和mq_receive函数 64 5.5 消息队列限制 67 5.6 mq_notify函数 68 5.7 Posix实时信号 78 5.8 使用内存映射I/O实现Posix消息队列 85 5.9 小结 101 习题 101 第6章 System V消息队列 103 6.1 概述 103 6.2 msgget函数 104 6.3 msgsnd函数 104 6.4 msgrcv函数 105 6.5 msgctl函数 106 6.6 简单的程序 107 6.7 客户-服务器例子 112 6.8 复用消息 113 6.9 消息队列上使用select和poll 121 6.10 消息队列限制 122 6.11 小结 124 习题 124 第三部分 同步 125 第7章 互斥锁和条件变量 126 7.1 概述 126 7.2 互斥锁:上锁与解锁 126 7.3 生产者-消费者问题 127 7.4 对比上锁与等待 131 7.5 条件变量:等待与信号发送 132 7.6 条件变量:定时等待和广播 136 7.7 互斥锁和条件变量的属性 136 7.8 小结 139 习题 139 第8章 读写锁 140 8.1 概述 140 8.2 获取与释放读写锁 140 8.3 读写锁属性 141 8.4 使用互斥锁和条件变量实现读写锁 142 8.5 线程取消 148 8.6 小结 153 习题 153 第9章 记录上锁 154 9.1 概述 154 9.2 对比记录上锁与文件上锁 157 9.3 Posix fcntl记录上锁 158 9.4 劝告性上锁 162 9.5 强制性上锁 164 9.6 读出者和写入者的优先级 166 9.7 启动一个守护进程的唯一副本 170 9.8 文件作锁用 171 9.9 NFS上锁 173 9.10 小结 173 习题 174 第10章 Posix信号量 175 10.1 概述 175 10.2 sem_open、sem_close和sem_unlink函数 179 10.3 sem_wait和sem_trywait函数 180 10.4 sem_post和sem_getvalue函数 180 10.5 简单的程序 181 10.6 生产者-消费者问题 186 10.7 文件上锁 190 10.8 sem_init和sem_destroy函数 191 10

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值