Unix 环境编程: 进程控制
来源:互联网 发布:大数据分析工程师 编辑:程序博客网 时间:2024/05/16 00:29
进程是程序在内存中运行的一个实例,这里说出了进程与程序的一个区别就是进程是在内存空间里的,程序实际上是存储在你的硬盘上的那个文件,又可以叫做可执行文件。
1. 如何标识一个进程
1)当然是用一个ID来标识一个进程啦,在Linux系统上貌似任何东西的标识都是使用一个整数也就是ID,进程当然也不例外,那么一个进程除了PID 以外还有哪些标识呢?
-->PID : 最常用的就是 process ID
-->PPID : 所有的进程都有一个父进程 Parent Process ID
-->UID : 用户ID,谁运行的这个进程
-->EUID : 有效用户ID,虽然一个进程是由用户A执行起来的,但是进程拥有的权限却可以与A不同, 这个EUID是跟可执行文件的S权限息息相关的
-->GID : 组ID
-->EGID :有效组ID
PID与PPID很直白很容易理解, UID/GID也还可以,毕竟进程都是由用户执行起来的嘛, 但是EUID?EGID是什么鬼?关于这两个ID,大家可以使用系统调用getUID() getEUID(),来观察一下,EUID与UID一般情况下是一样的,那么什么情况下会不一样呢 ?那就是当设置可执行文件的SUID这个权限的时候,也是使用了chmod u+s XXX。
SUID又是什么鬼? OK,看下面的实验吧
首先写一个测试程序
<span style="font-family:Courier New;"><span style="font-family:Courier New;">#include <stdio.h>#include <unistd.h>int main(){ uid_t uid = getuid(); uid_t euid = geteuid(); printf("uid = %d, euid = %d\n", uid, euid); return 0;}</span></span>
gcc test.c -o test 之后得到 可执行文件 : test
<span style="font-family:Courier New;"><span style="font-family:Courier New;">ls -l-rwxrwxr-x 1 gengj gengj 13451 8月 22 10:25 test<span style="color:#ff0000;">chown root testchmod u+x test</span>ls -l -rwsrwxr-x 1 root gengj 13451 8月 22 10:25 test</span></span>看到没有, 刚开始 test 文件的 用户和组都是gengj, 这个时候执行test,得到 uid == euid,但是当执行了后边的操作也就是设置了S权限之后(这个时候实际上test是以root权限来运行的,也就是它的有效用户是 root),再执行,可以看到euid变成了 0, UID没有改变。关于GID/EGID与上面的描述是一样的。
2)现在我们知道了如何获得关于进程的各种ID,那么怎么对这些ID进程设置呢?
实际上PPID是没有办法改变的,PID是内核分配的,这两个ID是不能改变的,也没有必要改变。可以改变的是UID EUID GID EGID, 这里我们不讨论组只讨论用户 ID, 因为他们的操作是一样的。
设置UID我们可以使用系统调用 setuid(uid_t uid); 关于这个函数需要说明一下:
-->如果进程拥有root权限的话,该进程调用setuid(uid) 可以把 本进程的UID, EUID, saved set-uid全部设置成参数 uid;
-->如果进程不是root权限,参数uid==UID 或者 uid == SUID的话, 本函数将会把EUID设置成uid,其他的ID都不变。
各种ID搞得人比较晕,我也不是特别的清楚, 不过只要记得不同的UID会使得进程拥有不同的权限,如果设置了EUID, 则表示了进程的实际可以拥有的权限。
2. 创建新的进程 fork
1)系统调用 pid_t fork(void) 是用来创建一个新的子进程的, 这个函数比较特殊,一次调用会返回两次:
<span style="font-family:Courier New;">#include <stdio.h>#include <unistd.h>int main(){ pid_t pid; int i = 0; if ((pid = fork()) < 0) { printf("fork failed\n"); return -1; } else if (pid == 0) { // this is child process printf("this is in child process\n"); i++; } else { //this is in parent process sleep(2); printf("this is in parent process\n"); } //this is in bothprocess printf("i = %d\n", i); return 0;}</span>
<span style="font-family:Courier New;"><pre style="-webkit-user-select: text; position: absolute; top: -99px;">this is in child processi = 1this is in parent processi = 0</span>
this is in child processi = 1this is in parent processi = 0
输出:-------------------------------------------------------------------------------------------
this is in child processi = 1this is in parent processi = 0
this is in child processi = 1this is in parent processi = 0
this is in child processi = 1this is in parent processi = 0
this is in child processi = 1
...(sleep 2)
this is in parent process
i = 0
-------------------------------------------------------------------------------------------
如上显示的, fork返回的pid如果为0 则表示这是从子进程返回的值,如果大于 0则表示是从父进程返回的值,返回值就是子进程的PID。
如上显示的,fork之后,父子进程都会继续执行接下来的代码,也就是说父子进程共享一个代码段 (text segment),但是数据是不一样的,因为在子进程对 i 赋值并不会影响父进程中的 i,说明,父子进程的数据段是不一样的,实际上子进程复制了父进程的数据段,所以导致父进程与子进程都有一个数据 i 的copy,各自的改变并不会影响另一个进程。
2) fork中的文件描述符
父进程中打开的文件描述符将在子进程中同样有效,也就是说父子进程共享文件描述符。这种共享会导致父子进程产生竞争现象,需要在编程中避免这种竞争。
3)子进程继承的东东与没有继承的东东
先看看有哪些东西没有被子进程继承吧:
-- PID & PPID
-- time (tms_utime, tms_stime) 在子进程中被置 0
-- 文件锁 在子进程中没有
-- 定时器
-- 信号集
被继承下来的东西:
-- UID , GID
-- 进程组
-- 会话(session) ID
-- 控制终端
-- set-user-id/ set-group-id
-- root directory
-- current work directory
-- 文件权限掩码 mask
-- 信号掩码
-- close-on-exec 标志
-- 环境变量
-- 共享内存
-- 内存映射
4) fork为什么会失败
fork失败意味着系统不能生成新的进程啦,这有可能是由于系统资源不足造成的, 也有可能是一个real user id 的进程数超出了限制造成的。
5)fork 与 exec函数
fork会产生一个新的进程,子进程会有父进程地址空间的一份copy, 但是如果在fork之后的子进程中调用exec函数,那么子进程将会被新的程序替代,子进程将会从新程序的main函数开始执行。
这里替换的意思不是创建一个新的进程,而是继续在原来的地址空间里面继续执行,只不过代码段,数据段,堆栈全部被替换啦,而且当新程序结束后也不再返回到子进程中啦。既然是在原子进程空间里面运行新的程序,PID当然还是不会改变的啦。
<span style="font-family:Courier New;">#include <stdio.h>#include <unistd.h>int main(){ pid_t pid; if ((pid = fork()) < 0) { printf("fork failed\n"); return -1; } else if (pid == 0) { // this is child process printf("this is in child process\n"); printf("exec a new program\n");</span>
<span style="font-family:Courier New;"> execlp("ls", "ls", "-1", NULL); <span style="color:#ff0000;">// run another program</span> printf(" I am back into child process\n"); <span style="color:#ff0000;">// never come back to here</span> } else { sleep(2); printf("this is in parent process\n"); global_num++; } //this is in bothprocess printf("i = %d, global_num = %d\n", i, global_num); return 0;}</span>
讲到了exec 函数,有必要提一下另外一个系统调用system(), 实际上system()函数是用exec函数来实现的,只不过多了一些错误检查。这个函数用于在我们的程序中执行另外的可执行文件,直到结束,也就是说它是阻塞的,一个简单的system实现如下.
parent-process --> fork --> exec
|-----------------------------waitpid()
3 进程结束
结束一个进程有很多种方法:
正常的退出
-- main函数中调用 return 或者 exit(),这两者是等价的
-- 调用 _exit 或者 _Exit
不正常退出
-- 调用abort, 将会产生SIGABRT信号
-- 接收到特定的信号, 比如运行中我们按下ctl-c也就是发送了TERM 信号给进程
不正常的退出现在先不讲,只讨论一下正常的退出中的exit _exit _Exit
1) exit 与return一样是一种比较安全温和的退出方式,将会flush 所有IO然后关闭所有的文件, 依次调用注册的at_exit 函数, 最后退出
2)_exit 与_Exit这是一种简单粗暴的退出方式,不会调用at_exit注册的函数,也可能不会flush IO (it depends on OS implementation)。
当子进程正常退出时,父进程可以获得子进程的退出状态值,退出状态值是exit函数的参数,当子进程非正常退出时,内核来把退出状态值送给父进程,总之,父进程总是可以获得子进程的退出状态值。 这里我们考虑以下两种情况
1)父进程先于子进程退出
如果父进程比子进程先退出,那么init将会成为子进程的父进程,这是因为Linux要确保每一个进程都有一个父进程
2)子进程先于父进程退出
如果子进程先退出啦,那么内核会在内存中维护这个子进程的一些信息,以便父进程在调用wait或者waitpid时能够获得子进程的退出状态,一般来讲内核保存的信息必然会包括PID和退出状态值, 因为这些都是wait函数需要的。
子进程退出后,如果父进程调用了wait或者waitpid,那么内核就不需要在保存这些残留信息了,那么这个子进程就完全退出了。如果父进程不调用wait&waitpid,那么这个子进程就变成了僵尸进程(zombie)。
是时候说说wait & waitpid函数了,这两个函数都会使得内核对子进程进行清理,也就是消除zombie。 我们先来讲讲这两个函数的特点吧
1)wait: 阻塞的,直到有一个子进程退出他才返回,实际上任何一个子进程的退出都会导致wait返回,也就是说,如果有很多子进程就需要调用很多次wait;如果没有子进程的话,wait也会立即返回,只不过是返回error。
2) waitpid: 相对于wait,waitpid比较人性化,他会等待特定的有其参数指定的进程退出。他可以是阻塞的也可以通过其参数配置成非阻塞的。haha, 可以配置的这个特性不错,大家可以试试看,这里不多说。
总结
这篇文章讲述了进程的创建fork exec, 进程的退出 exit, 如何避免僵尸进程waitpid,并给了简单的demo来说明fork等的应用。本文并没有讲述在什么情形中使用子进程这个特性,算是不足之处,以后尽量补上。
欢迎阅读提问,如果有不对的地方,请指正。
- Unix 环境编程: 进程控制
- 【温故而知新】Unix环境编程之进程控制
- Unix环境高级编程--进程控制
- Unix环境高级编程之进程控制
- 《UNIX环境高级编程》--8进程控制
- 《Unix环境高级编程》之 进程控制
- Unix环境编程之 进程控制
- unix环境高级编程-进程控制
- unix环境高级编程--进程环境与进程控制
- 《unix高级环境编程》进程控制——进程ID
- 《unix高级环境编程》进程控制——创建进程
- 《unix高级环境编程》进程控制——进程等待
- 《unix高级环境编程》进程控制——进程时间
- 《unix高级环境编程》进程控制——进程ID
- 《unix高级环境编程》进程控制——创建进程
- 《unix高级环境编程》进程控制——进程等待
- 《unix高级环境编程》进程控制——进程时间
- 《UNIX环境高级编程》第三部分进程之进程控制
- SimpleAdapter使用简单介绍
- onScrollChanged()方法解析
- Qt之QNetworkAddressEntry
- Android逆向工程-第三部分
- 快速开发数据分析系统
- Unix 环境编程: 进程控制
- linux驱动-I2C设备
- 求桥和割点的Tarjan算法
- GM11 C语言实现
- 聊一聊Netty TCP粘包/拆包问题的解决办法
- DSO(dsoframer)的接口文档在VC++使用
- [LeetCode]--131. Palindrome Partitioning(backTracking && DFS && DP)
- Effective Java之对所有对象通用的方法
- Observer模式