进程与系统调用、进程间通信--Head First C读书笔记

进程与系统调用

进程

进程是存储器中运行的程序。Windows通过taskmgr查看,Linux通过ps -ef查看系统中运行的进程。操作系统用一个数字来标识进程,它叫进程标识符(process identifier,简称PID)。

system()函数

system()函数接收一个字符串参数,并把它当成命令执行

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

char* now()
{
    time_t t;
    time(&t);
    return asctime(localtime(&t));
}

int main()
{
    char comment[80];
    char cmd[20];
    fgets(comment, 80, stdin);

    /*
     * int sprintf(char *str, char * format [, argument, ...]);
     * str为要写入的字符串;format为格式化字符串,与printf()函数相同;argument为变量
     */
    sprintf(cmd, "echo '%s %s' >> reports.log", comment, now());
    system(cmd);
    return 0;
}

编译、执行

[root@molaifeng process]# gcc system.c -o system
[root@molaifeng process]# ./system 
hello world
[root@molaifeng process]# ll
总用量 16
-rw-r--r-- 1 root root   39 1017 13:48 reports.log
-rwxr-xr-x 1 root root 7377 1017 13:48 system
-rw-r--r-- 1 root root  320 1017 13:48 system.c
[root@molaifeng process]# cat reports.log 
hello world
 Mon Oct 17 13:48:43 2016

但这样书写,并不安全

[root@molaifeng process]# ./system 
' && ls / && echo '

bin  boot  cgroup  dev  etc  home  lib  lib64  lost+found  media  misc  mnt  net  opt  proc  root  sbin  selinux  srv  sys  tmp  usr  var

刚刚的例子在程序中注入了一段“列出根目录内容”的代码,它也可以删除文件获启动病毒

exec()函数

exec()函数,告诉操作系统想运行哪个程序,通过运行其他程序来替换当前进程,在unistd.h头文件中。可以告诉exec()函数要使用哪些命令行参数和环境变量。新程序启动后PID和老程序一样,就像两个程序接力跑,你的程序把进程交给了新程序。

这里写图片描述

这里写图片描述

excele

execle.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main()
{
    /*
     * 最后一个NULL必须
     */
    char *my_env[] = {"JUICE=peach and apple", NULL};
    if (execle("diner_info", "diner_info", "4", NULL, my_env) == -1) {
        /*
         * strerror在string.h中
         * errno是变量定义在errno.h中的全局变量
         * EPERM=1 不允许操作
         * ENOENT=2 没有该文件或目录
         * ESSCH=3 没有该进程
         * EMULLET=81 这个值任何系统都不存在
         */
        puts(strerror(errno));
    }
    return 0;
}

diner_info.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    printf("Diner: %s\n", argv[1]);
    printf("Juice: %s\n", getenv("JUICE"));
    return 0;
}

编译、执行

[root@molaifeng process]# gcc execle.c -o execle
[root@molaifeng exec]# ./execle 
Diner: 4
Juice: peach and apple
excel

excel.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main()
{
    if (execl("/sbin/ifconfig", "/sbin/ifconfig", NULL) == -1) {
        fprintf(stderr, "Cannot run ifconfig: %s", strerror(errno));
    }
    return 0;
}

编译、执行

[root@molaifeng process]# gcc excel.c -o excel
[root@molaifeng process]# ./excel 
docker0   Link encap:Ethernet  HWaddr 00:00:00:00:00:00  
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::a039:4fff:feff:86f0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:147108 errors:0 dropped:0 overruns:0 frame:0
          TX packets:270728 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:84534107 (80.6 MiB)  TX bytes:438248842 (417.9 MiB)

eth1      Link encap:Ethernet  HWaddr 00:0C:29:13:02:E8  
          inet addr:10.254.21.122  Bcast:10.254.21.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe13:2e8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:22515732 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6319744 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:4312409200 (4.0 GiB)  TX bytes:7210961606 (6.7 GiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:2614124 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2614124 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1850652329 (1.7 GiB)  TX bytes:1850652329 (1.7 GiB)


/*
 * 当系统找不到你想运行的程序时,就会把errno变量设置
 * 为ENOENT
 * 如上述代码如果没有写路径
 */
[root@molaifeng exec]# ./execlp 
Cannot run ifconfig: No such file or directory

fork()函数

fork()函数,克隆当前进程,新建副本将从同一行开始运行相同程序,变量和变量中的值完全一样,只有进程标识符(PID)和原进程不同。原进程叫父进程,而新建副本叫子进程

fork() + exec()运行子进程
1 第一步用fork()系统调用复制当前进程。
1.1 进程需要以某种区分自己是父进程还是子进程,为此fork()函数向子进程返回0,向父进程返回非零值。
2 如果是子进程,就exec()。
2.1 这一刻,有两个完全相同的进程在运行,它们使用相同的代码,但子进程(从fork()接收0的那个)现在需要调用exec()运行程序替换自己
3 现在有两个独立进程,完全不受干扰。
4 为了让fork进程变快,操作系统使用“写时复制”(COPY ON WRITE, COW)

fork.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    /*
     * 会立即终止程序,并把退出状态置为1
     */
    exit(1);
}

int main(int argc, char *argv[])
{
    char *cmd[] = {"/sbin/ifconfig", "/bin/uname"};
    int i = 0;
    for (i = 0; i < 2; i ++) {
        /*
         * fork()会返回一个整型值:为子进程返回0,为父进程返回一个整数,父进程将接收到子进程的进程标识符
         * 不同操作系统用不同的整数类型保存进程ID,有的用short,有的用int,操作系统使用哪种类型,pid_t就设为哪个
         * 如果fork()返回-1,就说明在克隆进程时出了问题
         */
        pid_t pid = fork();
        if (pid == -1) {
            error("Can't fork process");
        }
        if (pid == 0) {
            if (execl(cmd[i], cmd[i], NULL) == -1) {
                error("Can't exec process");
            }
        }
    }
    return 0;
}

编译、执行

[root@molaifeng process]# gcc fork.c -o fork
[root@molaifeng process]# ./fork 
[root@molaifeng process]# docker0   Link encap:Ethernet  HWaddr 00:00:00:00:00:00  
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::a039:4fff:feff:86f0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:147108 errors:0 dropped:0 overruns:0 frame:0
          TX packets:270728 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:84534107 (80.6 MiB)  TX bytes:438248842 (417.9 MiB)

eth1      Link encap:Ethernet  HWaddr 00:0C:29:13:02:E8  
          inet addr:10.254.21.122  Bcast:10.254.21.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe13:2e8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:22521008 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6319910 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:4312922254 (4.0 GiB)  TX bytes:7210997918 (6.7 GiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:2614124 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2614124 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1850652329 (1.7 GiB)  TX bytes:1850652329 (1.7 GiB)

Linux

可以看出,ifconfig和uname两个命令都执行了;如果去掉fork()函数的话,就只会执行ifconfig命令,因为exec()函数通过运行新程序来替换当前程序,原来的程序就立刻终止了。

小结

这里写图片描述

进程间通信

文件描述符

进程内部一瞥,进程用文件描述符表示数据流,所谓的描述符其实就是一个数字。进程会把文件描述符和对应的数据流保存在描述符表中。文件描述符描述的不一定是文件。创建进程后,标准输入连接到键盘,标准输出和标准错误连接到屏幕。它们会保持这样的连接,直到人们把它们重定向到了其他地方。描述表从0到255号。

文件描述符数据流含义
0键盘标准输入
1屏幕标准输出
2屏幕标准错误
#include <stdio.h>

int main()
{
    fprintf(stderr, "this is a error message\n");
    printf("this is a right message\n");
    return 0;
}

编译、执行

[root@molaifeng process]# gcc err.c -o err
[root@molaifeng process]# ./err 
this is a error message
this is a right message

程序执行,便把标准输出和标准错误都输出到屏幕了

/*
 * 2> 表示“重定向”标准错误
 * &1 表示“到标准输出”
 */
[root@molaifeng process]# ./err > 1.txt 2>&1
[root@molaifeng process]# cat 1.txt 
this is a error message
this is a right message

上例表明>重定向了标准输出和标准错误到文件。

fileno()函数和dup2()函数

这里写图片描述

fileno.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

int main()
{
    FILE *file = fopen("stores.txt", "w");
    if (!file)
        error("Can't open the file");

    /*
     * 把向标准输出流指向了stores.txt文本中
     * 当程序用fopen打开stores.txt文件时,操作系统把文件file注册到文件描述符表中
     * fileno(file)是文件描述符编号,而dup2函数设置了标准输出符号(1号),让它也指向了该文件
     */ 
    if (dup2(fileno(file), 1) == -1)
        error("Can't redirect standard output");
    printf("this is a test message\n");
    return 0;
}

编译、执行

[root@molaifeng exec]# gcc fileno.c -o fileno 
[root@molaifeng exec]# ./fileno 
[root@molaifeng exec]# cat stores.txt 
this is a test message

使用fileno()函数和dup2()函数改造下fork.c文件,使其由标准输出到写入文件。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

int main(int argc, char *argv[])
{
    char *cmd[] = {"/sbin/ifconfig", "/bin/uname"};
    FILE *file = fopen("stores.txt", "w");
    int i = 0;
    for (i = 0; i < 2; i ++) {
        pid_t pid = fork();
        if (pid == -1) {
            error("Can't fork process");
        }
        if (pid == 0) {
            if (dup2(fileno(file), 1) == -1) {
                error("Can't redirect standard output");
            }
            if (execl(cmd[i], cmd[i], NULL) == -1) {
                error("Can't exec process");
            }
        }
    }
    return 0;
}

编译、执行

[root@molaifeng exec]# gcc fork.c -o fork
[root@molaifeng exec]# ./fork 
[root@molaifeng exec]# cat stores.txt 
docker0   Link encap:Ethernet  HWaddr 00:00:00:00:00:00  
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::a039:4fff:feff:86f0/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:147108 errors:0 dropped:0 overruns:0 frame:0
          TX packets:270096 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:84534107 (80.6 MiB)  TX bytes:438111540 (417.8 MiB)

eth1      Link encap:Ethernet  HWaddr 00:0C:29:13:02:E8  
          inet addr:10.254.21.122  Bcast:10.254.21.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe13:2e8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:21131805 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5967550 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:4068910015 (3.7 GiB)  TX bytes:7115255014 (6.6 GiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:1687125 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1687125 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1278756100 (1.1 GiB)  TX bytes:1278756100 (1.1 GiB)
Linux

waitpid()函数

waitpid()函数会等待子进程结束以后才返回

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t pid;
    int status, i;
    pid = fork();
    if (pid == 0) {
        printf("This is the child process. pid =%d\n", getpid());
    } else {
        sleep(1);
        printf("This is the parent process, wait for child...\n");
        /*
         * waitpid接收三个参数
         * pid 父程序在克隆子进程时会得到子进程的ID
         * pid_status用来保存进程退出信息,因为waitpid需要修改pid_status,因此它不需是个指针
         * 选项,0,函数将等待进程结束【man waitpid】
         */
        if (waitpid(pid, &status, 0) == -1) {
            fprintf(stderr, "this is an error occur when waiting the sub process return");
            exit(1);
        }
        /*
         * 因为pid_status中保存了好几条信息,只有前8位表示进程的退出状态,可以用宏来查看这8位的值
         * 如果退出状态不是0,变输出错误
         */
        i = WEXITSTATUS(status);
        printf("child's pid =%d . exit status=%d\n", pid, i);
    }
    return 0;
}

编译、执行

[root@molaifeng process]# gcc wait.c -o wait
[root@molaifeng process]# ./wait
This is the child process. pid =41067
This is the parent process, wait for child...
child's pid =41067 . exit status=5

如果把代码中的sleep(1)给注释了,再编译、执行

[root@molaifeng process]# gcc wait.c -o wait
[root@molaifeng process]# ./wait
This is the parent process, wait for child...
This is the child process. pid =41150
child's pid =41150 . exit status=0

pipe()函数

上面学习了用exec()函数和fork()函数运行独立进程,也知道怎么把子进程的输出重定向到文件,但如何从子进程直接获取数据呢?在进程运行时实时读取它生成得数据,而不是等子进程把所有数据都发送到文件,再从文件中读取出来?

答案是有的,可以用管道连接进程。

/*
 * 将描述符放入数组中
 */
int fd[2];

/*
 * 将数组名传给pipe()函数
 * pipe()函数创建了管道,并返回两个描述符
 * fd[0]用来从管道读数据
 * fd[1]用来向管道写数据
 */
if (pipe(fd) == -1) {
    error("Can't create the pipe");
}

这里写图片描述

pipe.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

int main()
{
    pid_t pid;
    int status, fd[2];
    char buf[11];
    if (pipe(fd) == -1) {
       error("Can't create the pipe"); 
    }
    pid = fork();
    if (pid == -1) {
        error("Can't fork the process");
    }
    if (pid == 0) {
        dup2(fd[1], 1);
        close(fd[0]);
        sprintf(buf, "%s", "hello world");
        write(fd[1], buf, sizeof(buf));
    }
    dup2(fd[0], 0);
    close(fd[1]);
    read(fd[0], buf, sizeof(buf));
    printf("output: %s\n", buf);
    return 0;
}

编译、执行

[root@molaifeng process]# gcc pipe.c -o pipe 
[root@molaifeng process]# ./pipe 
output: hello world

信号

#include <stdio.h>

int main()
{
    char name[30];
    puts("Enter your name:");
    fgets(name, 30, stdin);
    printf("Hello %s\n", name);
    return 0;
}

编译、执行

[root@molaifeng process]# gcc sig.c -o sig
[root@molaifeng process]# ./sig 
Enter your name:
^C
[root@molaifeng process]#

当运行./sig后,程序正在等待从键盘读取数据,这时按了CTRL+C,程序就停止运行了。为什么会这样?程序还没有执行到第二个printf()就已经退出了,所以CTRL+C停止的不仅仅是fgets(),而是整个程序。是操作系统停止了程序,还是fgets()函数调用 了exit()?这中间到底发生了什么?

奥秘发生在操作系统中,当调用fgets()函数时,操作系统会从键盘读取数据,但当看到用户按了CTRL+C,就会向程序发送中断信号。

信号是一条短信息,即一个整型值。当信号到来时,进程必须停止手中一切工作去处理信号。进程会查看信号映射表,表中每个信号都对应一个信号处理器函数。终端信号的默认信号处理器会调用exit()函数。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

/*
 * 新的信号处理器
 * 操作系统把信号传给处理器
 */
void diediedie(int sig)
{
    puts("Goodbye cruel worl ...\n");
    exit(1);
}

/*
 * 注册处理器的函数
 */
int catch_signal(int sig, void (*handler)(int))
{
    struct sigaction action;
    action.sa_handler = handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    return sigaction(sig, &action, NULL);
}

int main()
{

    /*
     * SIGINT表示我们要捕捉的中断信号
     * 将中断处理程序设为diediedie()函数
     */
    if (catch_signal(SIGINT, diediedie) == -1) {
        fprintf(stderr, "Can't map the handler\n");
        exit(2);
    }
    char name[30];
    puts("Enter your name:");
    fgets(name, 30, stdin);
    printf("Hello %s\n", name);
    return 0;
}

编译、执行

[root@molaifeng process]# ./sig 
Enter your name:
^CGoodbye cruel worl ...
kill 信号

执行sig程序

[root@molaifeng process]# ./sig 
Enter your name:
^CGoodbye cruel worl ...

打开另一个终端,用kill向程序发送信号

[root@molaifeng ~]# ps -a
   PID TTY          TIME CMD
 41514 pts/3    00:00:00 sig
 41536 pts/0    00:00:00 ps
[root@molaifeng ~]# kill 41514
[root@molaifeng ~]# kill 41514
-bash: kill: (41514) - 没有那个进程

以上kill命令将向进程发送信号,然后运行进程中配置好的处理函数。但有一个例外,代码捕捉不到SIGKILL信号,也没法忽略它。也就是说,即使程序有一个错误导致进程对任何信号都视而不见,还是能用kill -KILL结束进程。

[root@molaifeng process]# ./sig 
Enter your name:
已终止
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值