APUE学习笔记【4】

进程环境
1、exit和_exit函数
_exit函数直接进入内核,exit先执行 清理流程,如关闭标准IO、执行终止程序,然后进入内核。
对于main函数,一般return终止的流程用exit来表示exit(main(argc, argv))。

一、atexit函数
程序退出时,可以通过atexit函数预置终止处理程序:int atexit(void(* func)(void)。

void myexit()
{
    static int n = 0;
    n++;
    printf("\nmyexit handle %d times!\n", n);
    main();
}
int main(int argc, char*argv[])
{
    printf("hello!\n");

    (void)atexit(myexit);

    return 0;
}

这里写图片描述
2、进程存储空间布局
a)正文段。存储机器指令,只读且共享,一般只有一份
b)初始化数据段。存储全局变量,且全局变量有初始化
c)Bss非初始化数据段。存储未初始化的全局变量,程序执行前,内核会将该段内存置为0。
测试:
char g_iTest1;
char g_iTest2 = 2;
char g_iTest3;

root@ubuntu:/home/xie/study/test# ./test1231
1: 0x80d1128, 2:0x80cf028, 3: 0x80d1129
由上诉地址可以看出,全局变量初始化和未初始化的分开放置

d)栈。存放函数内自动变量(由系统自动分配内存和收回的变量,非static局部变量)、临时变量、函数调用者的环境信息。
e)堆。分配动态内存。
如下函数测试上诉各分段的内存位置:

char g_iTest1;
char g_iTest2 = 2;
char g_iTest3;

void myexit()
{
    static char n = 0;
    n++;
    printf("\nmyexit addr: %p!\n", &n);
}

int main(int argc, char*argv[])
{
    char n1 = 0;
    static char n2;
    char n3[2];

    char *buf = (char *)malloc(10);
    printf("hello!1: %p, 2:%p, 3: %p n1: %p n2: %p n3[0 1]: %p %p, buf[0 1]: %p %p\n", &g_iTest1, &g_iTest2, &g_iTest3, &n1, &n2, &n3[0], &n3[1], &buf[0], &buf[1]);

    (void)atexit(myexit);
    free(buf);
    return 0;
}

root@ubuntu:/home/xie/study/test# ./test1231
hello!1: 0x80d1128, 2:0x80cf028, 3: 0x80d1129 n1: 0xbfeb8e6f n2: 0x80cf7a0 n3[0 1]: 0xbfeb8e6d 0xbfeb8e6e, buf[0 1]: 0x82696a8 0x82696a9

myexit addr: 0x80cf7a1!

可以看出
除了栈,其他段都是从低地址向高地址分配空间,而栈是自顶向下;
地址空间由小到大分别存放:初始化全局变量->静态变量->未初始化全局变量->动态内存->局部变量

3、内存管理函数
常用函数有malloc、memset、free、calloc。

进程控制
进程ID 0是调度进程,也被叫做swapper进程,是一个系统进程。
进程ID 1是init进程,该进程始终运行,负责在内核自举后启动一个unix系统,它是以超级用户权限运行的普通用户进程,不是系统进程。同时它是所有孤儿进程的父进程。
获取进程信息的函数:
这里写图片描述
创建进程的函数:pid_t fork(void)。进程创建后,子进程继续执行后续指令,同时拥有父进程的数据拷贝。

    printf("parent \n");

    if (fork() == 0)
    {
        printf("child\n");

        exit(0);
    }

root@ubuntu:/home/xie/Desktop/test# ./test
parent
child
root@ubuntu:/home/xie/Desktop/test# ./test >abc.txt
root@ubuntu:/home/xie/Desktop/test# more abc.txt
parent
parent
child
root@ubuntu:/home/xie/Desktop/test#

观察以上回显,可以验证标准IO在终端上是行缓存的,在非终端设备上是全缓存的。创建子进程时,父进程数据空间中的缓存也会被复制,打印的字符在子进程中重复打印了一次。

Vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,子进程调用e x e c或e x i t之前,它在父进程的空间中运行。
同时,vfork保证子进程先执行,子进程exit或exec之后,才会调度父进程。

wait函数:
pid_t wait(int *statloc) ;
pid_t waitpid(pid_t pid, int *statloc, int options) ;
示例:

    printf("parent \n");

    if ((pid = fork()) == 0)
    {
        printf("child\n");

        sleep(3);

        return 0;
    }

    int status = 0;

    while ((pid = waitpid(pid, &status, 1)) == 0)
    {
        sleep(1);
        printf("parent wait\n");
    }

    printf("parent finish: PID = %d, status = %x\n", pid, status);

root@ubuntu:/home/xie/study/test# ./test0101
parent
child
parent wait
parent wait
parent wait
parent finish: PID = 2436, status = 0

进程组:
每个进程除了进程ID外,还属于一个进程组。每个进程组有一个组长进程,组长进程的ID与进程组ID相等。
int setpgid(pid_t pid, pid_t pgid).

信号

信号是软件中断。它提供了一种处理异步事件的方法,但是并不可靠。

signal函数是信号机制中最基本的函数,原型:
这里写图片描述

void sigact(int signo)
{
        printf("signo: %d \n", signo);
printf("getpid:%d  getppid:%d  getpgrp:%d \n", getpid(), getppid(), getpgrp());
}


int main(int argc, char*argv[])
{
        pid_t pid = 0;

        char *buf = (char *)malloc(BUFFSIZE);
        printf("parent, getpid:%d  getppid:%d \n", getpid(), getppid());

        signal(SIGUSR1, sigact);

        if ((pid = fork()) == 0)
        {
                printf("child, getpid:%d  getppid:%d\n", getpid(), getppid());
                pause();
                exit(0);
        }
        sleep(3);
        kill(pid, SIGUSR1);
        wait(0);
        for(;;)
        {
                pause();
                signal(SIGUSR1, sigact);
        }
        free(buf);
        return 0;
}

回显:
root@ubuntu:/home/xie/Desktop/test# ./test &
[1] 2476
root@ubuntu:/home/xie/Desktop/test#
parent, getpid:2476 getppid:2442
child, getpid:2477 getppid:2476

root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2477 getppid:2476 getpgrp:2476

root@ubuntu:/home/xie/Desktop/test# ps x | grep test
2476 pts/13 S 0:00 ./test
2479 pts/13 S+ 0:00 grep –color=auto test
root@ubuntu:/home/xie/Desktop/test# kill -USR1 2476
root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2476 getppid:2442 getpgrp:2476

root@ubuntu:/home/xie/Desktop/test# kill -USR1 2476
root@ubuntu:/home/xie/Desktop/test#
signo: 10
getpid:2476 getppid:2442 getpgrp:2476

可以通过设置信号处理函数为:SIG_DFL来还原对信号的响应方式,或者SIG_IGN设置忽略某信号。

时钟函数alarm:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

该函数在经过设置的秒数后,产生一个信号SIGALRM,该信号默认动作是终止进程。

void sigact(int signo)
{
        alarm(3);
        printf("\nsigno: %d \n", signo);
}


int main(int argc, char*argv[])
{
        printf("getpid:%d\n", getpid());
        signal(SIGALRM, sigact);
        alarm(3);

        for(;;)
                pause();
        return 0;
}

回显:
root@ubuntu:/home/xie/Desktop/test# ./test &
[1] 2970
root@ubuntu:/home/xie/Desktop/test# getpid:2970

signo: 14

signo: 14

signo: 14

signo: 14

signo: 14

上诉代码有一个缺陷,当只需要单次信号触发时,如果系统忙碌,时钟超时可能先于pause,这种情况下进程将一直等待。

kill与raise函数:

kill函数将信号发送给进程或进程组
int kill(pid_t pid, int signo);
pid大于0表示传递给目标进程,等于0表示有权限的进程组内所有进程。

int raise(int signo);
raise函数提供进程给自己发送信号的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值