进程和信号构成了LINUX操作环境最基础的部分。
它们控制了LINUX系统几乎所有的活动。
LINUX环境中进程如何被处理,如何准确地发现计算机在干什么,怎样启动或停止其他的进程,怎么使进程收发消息,如何避免僵尸进程?在这一章都会给出答案。
特别地,我们需要了解:
1.进程结构,类型和调度;2.开始新的进程的不同方式;3.父,子,僵尸进程;4.什么是信号,如何被使用。进程:是一个地址空间,带着若干个同时运行的线程,以及这些线程所需要的资源。
多任务操作系统会让许多程序一起跑,每个程序就构成1个进程。
进程结构
每个进程会被分配一个唯一的标识符 PID (范围 2--32768, init进程就是1)
#include
int getpid(void); //取得当前进程的PID
int getppid(void); //取得当前进程的父进程的PID
/proc目录下放了特定的进程文件
LINUX系统有虚拟存储系统,可以把超出物理存储空间以外的进程装进物理内存空间。
进程表
$ps -af
$ps -ax
进程调度
LINUX调度器基于优先级决定哪个进程该跑。高优先级的跑得更频繁,低优先级的会因为高优先级进程作好了准备而根本没法跑。
启动新的进程
1.system 函数
2.exec 系列函数 :代替当前执行的线程
int execv(const char* pathname, char *const argv[])
3.fork 函数 复制新的进程
在进程表中创建1个新的process entry,跑同样的代码,与当前进程有很多相同的属性,但是有自己的数据空间,环境和文件描述符。
#include
#include
pid_t fork(void);
Initial Process
|
Fork() ----------------------------
| |
return a new PID return 0
| |
Original Process continue New Process
fork失败,会返回-1,这通常是由于子进程数量的限制CHILD_MAX,相应的errno是EAGAIN. 如果没有足够的空间资源,errno会是ENOMEM
当fork被调用时,当前进程会分成2个进程,区分父进程的方式是返回非零值(即新创建的子进程的PID)。而子进程则是返回0。
4.等待进程
#include
#include
pid_t wait(int* stat_loc);
wait会导致父进程阻塞,直到它的1个子进程结束或者父根本没有子进程。这个调用会返回该子进程的PID。如果有错误,返回-1,错误码保存在errno中。
用宏WIFEXITED(stat_loc)判断子进程是否正常退出;
用宏WEXITSTATUS(stat_loc)返回子进程的退出码。
#include
#include
#include
#include
int main()
{
pid_t pid;
char *message;
int n;
int exit_code;
printf("fork program starting\n");
pid = fork();
switch(pid)
{
case -1:
perror("fork failed");
return 1;
case 0:
message = "This is the child";
n = 5;
exit_code=37;
break;
default:
message = "This is the parent";
n = 3;
exit_code=0;
break;
}
for(; n > 0; n--) {
puts(message);
sleep(1);
}
if(pid!=0){
printf("child PID= %d\n", pid);
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
if(-1==child_pid){
printf("errno: %d\n", errno);
}
printf("child process has finished: PID=%d\n", child_pid);
if(WIFEXITED(stat_val)){
printf("xxx %d\n", stat_val);
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
printf("xxx %d\n", stat_val);
}
else
printf("Child terminated abnormally\n");
}
return exit_code;
}
父进程创建子进程后,子进程一般要执行不同的程序.为了调用系统程序,我们可以使用系统调用exec族调用.
zombie
zombie进程又叫defunct进程
“僵尸”进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。
我们知道,每个Unix进程在进程表里都有一个进入点(entry),核心程序执行该进程时使用到的一切信息都存储在进入点。当用ps命令察看系统中的进
程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关
信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。当这个进程走完了自己的生命周期后,它会执行exit()系统调用,此时原来
进程表中的数据会被该进程的退出码(exit
code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。由此可见,defunct进程的出现时间是在子进
程终止后,但是父进程尚未读取这些数据之前。
线程
进程虽然可以通过共享内存或片段来完成一定的协作,但是本质上是分割的空间,不能共享变量。
线程可以,详细以后再讨论。
信号Signal
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。
信号只是用来通知某进程发生了什么某种异步事件,并不给该进程传递任何数据。
信号的操作有raise,catch两种。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。
进程之间可以互相通过系统调用kill发送软中断信号。
#include
#include
int kill(pid_t pid, int sig);
将信号sig传送给进程pid
void (*signal(int sig, void (*handler)(int)))(int);
设置信号sig的处理函数handler.当信号发生,handler被执行后,系统会恢复原来的预设处理函数。
#include
int pause(void);
令当前进程暂停进入睡眠状态,直到信号发生
信号类型
- 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
- 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
- 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
- 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
- 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
- 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
- 跟踪进程执行的信号
POSIX1.1中的信号包括
信号 值 处理动作 发出信号的原因
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGTRAP 5 C 跟踪/断点捕获
SIGABRT 6 C 由abort(3)发出的退出指令
SIGBUS 7 C 总线错误(错误的内存访问)
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 终止信号
动作A: terminate进程
动作B: 忽略
动作C: terminate进程并转储coredump
#include
#include
#include
#include
static int alarm_fired = 0;
void ding(int sig)
{
alarm_fired = 1;
}
int main()
{ pid_t pid;
printf("alarm app starting\n");
pid=fork();
switch(pid){
case -1:
perror("fork failed");
return 1;
case 0:
sleep(5);
kill(getppid(), SIGALRM);
return 0;
}
printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);
pause();
if (alarm_fired) printf("Ding!\n");
printf("done\n");
return 0;
}
#include
int alarm(int seconds);
经过指定的秒数后raise信号SIGALRM
#include
#include
void handler(int sig) {
printf(“hello\n”);
}
main()
{
int i;
signal(SIGALRM,handler);
alarm(5);
for(i=1;i<7;i++){
printf(“sleep %d ...\n”,i);
sleep(1);
}
}
阅读(956) | 评论(0) | 转发(0) |