一个诡异的linux c输出问题,望高手不吝赐教。。。

xiaowai0219 2010-04-11 09:42:16
/*程序功能:创建一个子进程,父进程和子进程交替运行。父进程先显示一次“Current time:”, 然后子进程每隔一秒显示一次当前系统日期和时间,共显示三次。以上过程共循环进行五次。*/

pid_t pid,ppid;
/*全局变量*/

void handle(int signo){

int i=0;

time_t now;

if(signo==SIGUSR1){

for(i=0;i<3;i++){

time(&now);

printf("%s",ctime(&now));

sleep(1);

}

}

if(signo==SIGUSR2){

printf("Current time:\n");

kill(pid,SIGUSR1);

}

}


int main(){
int i;
signal(SIGCLD,SIG_IGN);

signal(SIGUSR1,handle);

signal(SIGUSR2,handle);
pid=fork();

if(pid<0){
perror("fork");
exit(-1);
}
else if(pid==0){

ppid=getppid();
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);

pause();
}
exit(0);
}
wait(0);
return 0;
}

程序能通过编译链接,第一次执行的时候能理想输出,如下:
Current time:
Sun Apr 11 09:27:25 2010
Sun Apr 11 09:27:26 2010
Sun Apr 11 09:27:27 2010
Current time:
Sun Apr 11 09:27:28 2010
……
为什么之后再执行时,会出现同一个时间会输出两次情况,如下:
Current time:
Sun Apr 11 09:27:29 2010
Sun Apr 11 09:27:29 2010
Sun Apr 11 09:27:30 2010
Sun Apr 11 09:27:30 2010
Sun Apr 11 09:27:31 2010
Sun Apr 11 09:27:31 2010
Current time:
……
小弟百思不得其解,望各高手不吝赐教
...全文
355 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
xiaowai0219 2010-04-18
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 novawl 的回复:]
当fork()调用后,父进程和子进程共享地址空间(此时数据段的pid,ppid都为0),其后不管是父进程先返回还是子进程先返回,都要修改pid的值,就会发生缺页错误,内核将复制页面。你的程序的问题就出在这儿,在这个时间段执行kill(pid,SIGUSR1),pid的值就会是0(就是数据段的初始值)。
我在我的ubuntu9.04下测试过,第一次输出时,pid的值是0,后面就会变成子进程的id了。如果将pid初始成其他值,第一次输出pid的值时,也将是那个初始值。过段时间,内核的页复制完成时,pid就会变成子进程ID。
[/Quote]
我刚接触linux,对内核还不熟悉。看了些文章了解了下Copy-On-Write,觉得是这样的。并且这也能解释是否在子进程开始执行的时候让它sleep一下结果会不同了。
我用的也是ubuntu9.04,有一点不同的是并不只是第一次的pid是0。照你所说就应该是内核复制的问题了,那也不用3秒那么久吧。真是让人头疼。。。
xiaowai0219 2010-04-18
  • 打赏
  • 举报
回复
还是结帖算了,不想深究下去了。头疼。。。
xiaowai0219 2010-04-18
  • 打赏
  • 举报
回复
3秒是因为
for(i=0;i<3;i++){

time(&now);

printf("%s",ctime(&now));

sleep(1);

}
并不准确
我说的3秒是从fork返回到第二次执行上面那代码的时间
ghostwcy 2010-04-18
  • 打赏
  • 举报
回复
[Quote=引用 24 楼 xiaowai0219 的回复:]

引用 21 楼 novawl 的回复:
当fork()调用后,父进程和子进程共享地址空间(此时数据段的pid,ppid都为0),其后不管是父进程先返回还是子进程先返回,都要修改pid的值,就会发生缺页错误,内核将复制页面。你的程序的问题就出在这儿,在这个时间段执行kill(pid,SIGUSR1),pid的值就会是0(就是数据段的初始值)。
我在我的ubuntu9.04下测试过,第一次输出时……
[/Quote]
3秒你是怎么算出来的啊
子进程从fork返回,子进程向父进程发信号,到父进程执行信号处理程序,只是一瞬间的事情
只要父进程还没有来得及从fork返回时收到信号,就会发生这种异常了。
xiaowai0219 2010-04-17
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 ghostwcy 的回复:]
你代码有两个问题:
1. 进程同步
2. 使用了printf输出,printf输出只是将内容输出到缓冲区中,不能保证能够即时输出。
[/Quote]
printf("\n");不是可以刷新缓冲区么
我现在的问题是void handle2(int signo)函数是只可能是父进程在执行,多执行几次,为什么pid会出现不同的结果。
[Quote=引用 17 楼 iisbsc 的回复:]
你代码有两个问题:
这个是进程,不是线程,父子进程之间没有共享的变量的,也就是父进程的pid变量永远不是0,子进程的pid变量永远是0,不然Unix就乱了。
[/Quote]
这个我也同意。但是在这个程序里面,父进程执行的函数中pid的值确实会出现0。不明白。。。
天亮后说晚安 2010-04-17
  • 打赏
  • 举报
回复
printf?
sabflying 2010-04-17
  • 打赏
  • 举报
回复
这是信号处理函数的异步调用引起的,
在进程收到信号后,系统会试图将收到信号
的进程唤醒,而sleep进行阻塞的进程肯定会
被唤醒,而进程被唤醒后立刻执行信号处理函数.

此外,信号处理函数和主程序中是不能同时使用printf的
ghostwcy 2010-04-17
  • 打赏
  • 举报
回复
你代码有两个问题:
1. 进程同步
2. 使用了printf输出,printf输出只是将内容输出到缓冲区中,不能保证能够即时输出。
iisbsd 2010-04-17
  • 打赏
  • 举报
回复
我不同意关于pid怀疑,这个是进程,不是线程,父子进程之间没有共享的变量的,也就是父进程的pid变量永远不是0,子进程的pid变量永远是0,不然Unix就乱了。
novawl 2010-04-17
  • 打赏
  • 举报
回复
另外,在cygwin下没有出现这种情况可能是windows创建进程时并未使用写时复制技术。创建进程时就复制了父进程的地址空间。亦或是进程调度方面的原因,当执行kill(pid,SIGUSR1)时,内核已完成了复制。
才发现那段引用的颜色太难看了。
novawl 2010-04-17
  • 打赏
  • 举报
回复
问题应该出在写时复制(Copy-On-Write)上,引用一段关于写时复制的描述:
现在的Unix内核(包括Linux),采用一种更为有效的方法称之为写时复制(或COW)。这种思想相当简单:父进程和子进程共享页面而不是复制页面。然而,只要页面被共享,它们就不能被修改。无论父进程和子进程何时试图写一个共享的页面,就产生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。原来的页面仍然是写保护的:当其它进程试图写入时,内核检查写进程是否是这个页面的唯一属主;如果是,它把这个页面标记为对这个进程是可写的。
当fork()调用后,父进程和子进程共享地址空间(此时数据段的pid,ppid都为0),其后不管是父进程先返回还是子进程先返回,都要修改pid的值,就会发生缺页错误,内核将复制页面。你的程序的问题就出在这儿,在这个时间段执行kill(pid,SIGUSR1),pid的值就会是0(就是数据段的初始值)。
我在我的ubuntu9.04下测试过,第一次输出时,pid的值是0,后面就会变成子进程的id了。如果将pid初始成其他值,第一次输出pid的值时,也将是那个初始值。过段时间,内核的页复制完成时,pid就会变成子进程ID。
xiaowai0219 2010-04-16
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 elated 的回复:]
如果父进程先运行,子进程后运行,子进程在父进程后从fork返回,pid值就为0
pid中的地址就是后调度的进程从fork的返回值
[/Quote]
void handle2(int signo){
printf ("handlepid = %i , addr = %x\n", pid,&pid);

printf("Current time:\n");

kill(pid,SIGUSR1);

}


if(pid==0){
/*sleep(1);
*/
ppid=getppid();
printf ("childpid = %i , addr = %x\n", pid,&pid);
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);

pause();
}
exit(0);
}
printf ("parentpid = %i , addr = %x\n", pid,&pid);

加注释的那一行,结果:
parentpid = 3676 , addr = 804a050
childpid = 0 , addr = 804a050
handlepid = 3676 , addr = 804a050
Current time:
Fri Apr 16 23:25:48 2010
Fri Apr 16 23:25:49 2010
去掉那一行,结果:
childpid = 0 , addr = 804a050
handlepid = 0 , addr = 804a050
Current time:
Fri Apr 16 23:26:25 2010
Fri Apr 16 23:26:25 2010
Fri Apr 16 23:26:26 2010
Fri Apr 16 23:26:26 2010
那么是不是该认为跟你所说的相反呢,子进程先运行就应该是0,父进程先运行就是子进程的进程ID

最近都让这个东西给整晕了。有没有高手能帮忙彻底解决呢
elated 2010-04-16
  • 打赏
  • 举报
回复
好像是这个样子:
如果pid作全局变量,那么父子进程引用的是同一个地址空间
如果父进程先运行,子进程后运行,子进程在父进程后从fork返回,pid值就为0
pid中的地址就是后调度的进程从fork的返回值
局部变量在栈中分配,用不同的地址空间,就不会有这个问题

if(signo==SIGUSR2){
printf ("pid = %i , addr = %x\n", pid,&pid);
printf("Current time:\n");
fflush(0);
kill(pid,SIGUSR1); //如果pid为0,那么发信号给当前进程组,所以会输出两次

}
xiaowai0219 2010-04-14
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 cceczjxy 的回复:]

你改句话就明白了

printf("Current time: %d\n",pid);
[/Quote]
两次输出时,是因为父子进程都在执行
for (i=0;i<3;i++) {
time(&now);
printf("%s", ctime(&now));
sleep(1);
}
但是为什么多运行几次就会出现不同的情况呢, 我知道是进程同步没做好,但具体是哪,有什么不妥就有点想不通了。多多指教。。。
cceczjxy 2010-04-14
  • 打赏
  • 举报
回复
你改句话就明白了

printf("Current time: %d\n",pid);
xiaowai0219 2010-04-14
  • 打赏
  • 举报
回复
if (signo==SIGUSR2) {
printf("Current time:\n");
kill(pid, SIGUSR1);
}
这里的pid可能为0,此时将向父子进程发送信号。因此导致父子进程都去执行输出时间而出现两次相同的时间。。。改掉之后的代码如下。我暂时还有点不明白,为什么pid可能为0,不是fork()返回到父进程的是子进程的pid么
#include<stdio.h>
#include<time.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/wait.h>
pid_t cpid,ppid;


void handle1(int signo){

int i=0;

time_t now;

if(signo==SIGUSR1){

for(i=0;i<3;i++){

time(&now);

printf("%s%",ctime(&now));

sleep(1);

}

}

}


void handle2(int signo){

printf("Current time:\n");

kill(cpid,SIGUSR1);

}



int main(){
pid_t pid;
int i;
int stat;
signal(SIGCLD,SIG_IGN);

signal(SIGUSR1,handle1);

signal(SIGUSR2,handle2);
pid=fork();

if(pid<0){
perror("fork");
exit(-1);
}
else if(pid==0){
sleep(1);
/*确保cpid=pid;先运行*/
ppid=getppid();
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);

pause();
}
exit(0);
}
cpid=pid;
wait(&stat);
return 0;
}
xiaowai0219 2010-04-11
  • 打赏
  • 举报
回复

分开处理还是一样的。。。
在运行的时候我把系统监视器打开看到,如果是一次输出两个相同时间两个进程都是hrtimer_nanosleep状态
若只输出一次则一个hrtimer_nanosleep 一个 do_wait。。。对这些我一点不懂,不晓得是怎么回事
xiaowai0219 2010-04-11
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 wind_runner 的回复:]

楼主的进程间同步没做好

注意这句:
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);

子进程向父进程发送消息是连续发的,没有sleep

那么对于两个消息都用同一个消息处理程序,是不是会造成时序上的混乱?
那么这就跟这时候的系统有关系了,调度,消息排队......

楼主先把这个问题解决了,也许就解决你的问题了
[/Quote]
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);
pause();
}
每发送一次就把子进程挂起来了啊,不是连续发的
我先去把消息处理程序改掉看看
Wind_Runner 2010-04-11
  • 打赏
  • 举报
回复
楼主的进程间同步没做好

注意这句:
for(i=0;i<5;i++){
kill(ppid,SIGUSR2);

子进程向父进程发送消息是连续发的,没有sleep

那么对于两个消息都用同一个消息处理程序,是不是会造成时序上的混乱?
那么这就跟这时候的系统有关系了,调度,消息排队......

楼主先把这个问题解决了,也许就解决你的问题了
xiaowai0219 2010-04-11
  • 打赏
  • 举报
回复
有没有可能是跟操作系统有关呢,我用的还是Ubuntu9.04,在这个下面则出现上述情况
但是在sygwin(windows下面的一个仿linux终端)下面是可以得到理想结果的
加载更多回复(6)

23,120

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧