进程控制:创建 终止 等待
在Linux中fork函数时非常重要的系统调用函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
函数原型:
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
当一个进程调用fork之后,就有两个二进制代码相同的进程,而且它们都运行到相同的地方,每个进程都将可以开始它们自己的运行。

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。
注意,fork之后,谁先执行完全由调度器决定。
fork常规用法
exec函数。fork调用失败的原因
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,具体见下图:

进程退出场景:
C/C++里为我们提供了许多退出码,通过这些退出码,我们能知道代码运行完毕后的结果是否正确,如果不正确我们可以通过一些转化函数strerror()将退出码转换为退出信息,通过这些信息我们就可以知道知道为什么运行完毕结果不正确。
例如:我们写一些C语言代码时经常在结尾写上return 0,这个 0就是main()函数的退出码,我们之所以设置为0是因为0通常表示正常退出,其他数字表示异常退出。
下面我们来看看C语言为我们提供了那些退出码吧!
#include<stdio.h>
#include<string.h>
int main()
{
for(int i = 0; i < 150; ++i)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}

C语言大概为我们提供了130多个错误码,其中0表示成功,
在Linux中我们可以使用下面的命令查看:最近一次执行的进程的退出码!
echo $?
我们上一个进程就是test1c,而且我们设置的进程退出码就是0。我们来查看一下。

进程的退出其实就是OS内减少一个进程,OS释放进程对应的内核数据结构+代码和数据
正常终止:(可以通过echo $? 查看进程退出码)
异常退出:
ctrl + c,信号终止
kill -9 信号终止
main函数return
main函数return,进程正常结束,但结果是否正确未知。其他函数return呢? 其他函数return仅仅代表该函数返回,进程执行时本质是main执行流执行!
exit函数
函数原型 :

参数:status 定义了进程的终止状态,父进程可以通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用 ,所以exit(-1)时,在终端执行$?发现返回值是255。
在调用main的运行时函数会将main的返回值当做 exit的参数,所以main函数中的 return status;也等价于exit (status);
注意:在代码的任意地方调用exit函数都表示进程退出!
我们看下面一段代码了解一下该函数的使用:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void PrintError()
{
for(int i = 0; i < 150; ++i)
{
printf("%d: %s\n", i, strerror(i));
//打印一次就退出整个进程
exit(123);
}
}
int main()
{
PrintError();
return 0;
}

可以看到进程按照我们给的退出码退出了,并且exit函数是直接让进程退出而不是让调用他的函数退出。
_exit函数_exit函数是系统调用,它的使用与exit函数类似,参数也是一样的。
_exit函数,exit将进程退出时会刷新缓冲区,而_exit将进程退出时并不会刷新缓冲区!//exit函数退出
#include<stdio.h>
#include<stdlib.h>
int main()
{
//停顿一秒后打印出"you can see me ?"
printf("you can see me ?");
sleep(1);
exit(0);
printf("you can see also me ?");
return 0;
}
运行结果:

//_exit退出
#include <stdio.h>
#include <unistd.h>
int main()
{
//停顿一秒后直接退出不打印出"you can see me ?"
printf("you can see me ?");
sleep(1);
_exit(0);
printf("you can see also me ?");
return 0;
}

exit与_exit函数的对比

在学习进程等待之前,我们先来了解一下,进程为什么要进行等待?
等待:就是通过系统调用,获取子进程退出码或者退出信号,顺便释放内存问题。

进程等待的方法有两种:wait和 waitpid这两个函数都是Linux提供的系统调用。其中waitpid更加强大!具体详细使用可以使用man命令查看相关文档。

①wait函数
函数原型:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
作用:等待任意一个子进程的状态变化。
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL,如果为NULL 则wait函数仅仅起到回收子进程的作用
参数:status的详解
int的整形的不同区段来表示退出码与退出信号,因此status虽然是int类型但不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位,高16位被抛弃):
status的次低八位的数据表示进程的退出状态,即退出码。终止信号用最低7位表示,终止信号的前一位表示 core dump信号(这里不做解释)。
例如下面一段数据在 status中表示,退出码为 6 ,退出信号为 0。

那么当我们将status传递给wait函数后,得到的数据怎么拿出来呢?答案是:按位与 &
如果我们想要拿到status中的退出码,我们可以(status >> 8) &0xFF,想要拿到退出信号可以status &0x7F


不过,在实际使用过程中,我们没有必要采取上面比较原始的方法,Linux为我们提供给两个宏函数,通过它们我们也能达到我们想要的效果:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
那么下面我们来看一看wait函数的使用吧!
#include < stdio.h >
#include < string.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys / types.h >
#include < sys / wait.h >
int main()
{
pid_t id = fork();
if (id == 0)
{
//child process
int second = 3;
while (second)
{
printf("我是子进程,我还活着呢,我还有%dS\n", second--);
sleep(1);
}
exit(0);
}
else if (id < 0)
{
perror("fork() fail:");
return 0;
}
//parent process
//此时子进程已经死亡,我们可以看到子进程处于僵尸状态Z
sleep(5);
//定义一个整形变量,方便从wait中获取子进程的状态信息
int status = 0;
int Pid = wait( & status);
printf("我是父进程,等待子进程成功!子进程的pid是: %d, ", Pid);
//判断子进程是正常退出还是异常退出
if (WIFEXITED(status))
{
//如果是正常退出,就打印子进程退出码。
printf("子进程的退出码是: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程退出异常\n");
}
//等待两秒,方便观察到子进程僵尸状态消失。
sleep(2);
return 0;
}
代码运行结果:

在代码运行的过程中,打开另外一个窗口,输入下面的命令每隔一秒检测一下进程的状态变化
while :; do ps -axj | head -1 && ps -axj |grep test1c | grep -v grep ; sleep 1; echo "--------------"; done
我们可以看到下面的进程状态的变化:

这就是wait函数,以及进程退出信号退出码的使用,但是我们还要讨论一个问题,那就是,父进程在wait的时候在干什么呢?没错,父进程什么都没有做,就是在等待,但是有些时候,我们并不想让父进程在调用wait函数后只是进行等待,我们还想让父进程做一些其他的事情,这个时候我们就要使用功能更加强大的waitpid函数了。
②waitpid函数
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
参数:
pid:
Pid = -1,等待任一个子进程。与wait等效。
Pid > 0,等待其进程ID与pid相等的子进程。
status:
输出型参数,表示子进程的状态。
options:
0,若设置为 0 则表示调用 waitpid时父进程将处于阻塞等待状态,与wait函数类似。
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,继续运行父进程。若子进程正常结束,则返回该子进程的ID。
我们来看一下非阻塞等待的使用:
#include < stdio.h >
#include < string.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys / types.h >
#include < sys / wait.h >
int main()
{
pid_t id = fork();
if (id == 0)
{
//child process
int second = 3;
while (second)
{
printf("我是子进程,我还活着呢,我还有%dS\n", second--);
sleep(1);
}
exit(0);
}
else if (id < 0)
{
perror("fork() fail:");
return 0;
}
//parent process
int status = 0;
while (1)
{
//读取子进程的状态,进行非阻塞等待
int Pid = waitpid(id, &status, WNOHANG);
if (Pid > 0)
{
printf("我是父进程,等待子进程成功!子进程的pid是: %d, ", Pid);
if (WIFEXITED(status))
{
printf("子进程的退出码是: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程退出异常\n");
}
exit(-1);
}
//子进程状态若不改变,将会执行下面的代码
else if (Pid == 0)
{
printf("我是父进程,子进程还没有退出呢!我在做一做其他事情\n");
sleep(1);
}
//waitpid错误
else
{
perror("waitpid() fail:");
exit(-1);
}
}
return 0;
}
运行结果:

出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.