Linux实训笔记第二周P2

前言

接上文,具体多进程和多线程的编程!努力每一天!扬帆,启航!

Linux多进程编程

一.多进程编程基础
1.fork()
Linux下创建一个新进程的方法是使用 fork()函数。
$man fork:

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

(1)fork函数说明:
从已存在的进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。使用 fork()得到的子进程是父进程的一个复制,它从父进程处继承了整个进程的地址空间,包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根自录、资源限制和控制终端等,而子进程所独有的进程号、资源使用和计时器等。
父子进程一个很重要的区别是:fork()的返回值不同。父进程得到的值是子进程的进程号,当然失败就-1,而子进程中返回0,可以通过返回值来判定该进父进程还是子进程。
子进程没有执行 fork()函数,而是从 fork()函数调用的下一条语句开始执行
测试代码:

/*! \brief 测试fork函数
 *  \param void
 *
 *return void
 */
void processForkTest()
{
    pid_t result;
    //接收fork返回值以此作为判断父子进程标准
    result=fork();
    //儿子从这里运行,儿子得到的返回值为0
    if(-1==result)//创建失败
    {
        perror("fork error\n");
        return;
    }
    else if(0==result)//子线程
    {
    //getpid()和getppid()分别为获取当前,父进程的PID
        printf("我是子进程,PID是:%d\n",getpid());
        printf("我的父进程PID是:%d\n",getppid());
    }
    else//父线程
    {
        printf("我是父进程,PID是:%d\n",getpid());
        printf("我的子进程PID是:%d\n",result);
    }
}

运行之后

我是父进程,PID是:17653
我的子进程PID是:17654
我是子进程,PID是:17654
我的父进程PID是:17653

Process returned 0 (0x0)   execution time : 0.010 s
Press ENTER to continue.

2.exec 函数族
(1)exec 函数族提供在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代当前进程的数握段。代码段和堆栈段。在执行完之后,当前进程除了进程号外,他内容都被替换了。这里的可执行文件既可以是二进制文件也可以是Linux下任何可执行的脚本文件。
在 Linux中使用exec 函数族主要有两种情况。

①当进程认为自己不能再为系统和用户做出任何贡献时,就可以调exec 函数族中的任意一个函数让自己重生。

② 如果一个进程想执行另一个程序,那么它就可以调用 fork函数新创建个进程,然后调用exec 函数族中的任意一个函数,这样看起来就像通过执行程序而产生了一个新进程(这种情况非常普遍)。

(2)exec函数族语法
实际上,在Linux中并没有exec()函数,而是有6个以 exec 开头的函数

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                  char *const envp[]);

这6个函数在函数名和使用语法的规则上都有细微的区别,下面按文件查找方式、参数传递方式及环境变量这几个方面进行比较。

①查找方式。第一个参数是path的函数是查找完整的文件目录路径,而是file的函数系统就会自动按照环境变量PATH所包含路径查找!

②参数传递方式。exec 函数族的参数传递方式有两种:一种是逐个列|举的方式.另一种是将所有参数通过指针数组传递。
这里的参数实际上就是用户在使用这个可执行文件时所需的全部命令选项字特串(包括该可执行程序命令本身)。要注意的是**,这些参数必须以NULL结尾**

③环境变量。exec 函数族可以使用默认的环境变量,也可以传入指定的环境变量。这里的第三个参数envp中指定当前进程所使用的环境变量。

事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。在使用 exec 函数族是一定要加上错误判断语句。exec很容易执行失败,其中最常见的原因:
找不到文件或路径,此时errno被设置为 ENOENT。
数组 argv 和 envp 忘记用 NULL 结束,此时 errno 被设值EFAULT。
没有对应可执行文件的运行权限,此时errno被设置为EACCES

(3)随便测试一个:

/*! \brief 测试exec函数族
 *  \param void
 *
 *return void
 */
void processExecTest()
{
   if(0==fork())
   {
    if(execlp("ps","ps","-ef",NULL)<0)//相当于终端命令 ps -ef看进程信息
    {
      printf("调用失败\n");
    }
   }
}

运行结果

Process returned 0 (0x0)   execution time : 0.005 s
Press ENTER to continue.
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 May26 ?        00:00:01 /sbin/init
root         2     0  0 May26 ?        00:00:00 [kthreadd]
root         3     2  0 May26 ?        00:00:05 [ksoftirqd/0]
root         6     2  0 May26 ?        00:00:31 [migration/0]
root         7     2  0 May26 ?        00:00:03 [watchdog/0]
				..............

3.exit()和_exit()
(1)函数说明

NAME
       exit, _Exit, _exit - terminate a process

SYNOPSIS
       #include <stdlib.h>

       void exit(int status);
       void _Exit(int status);

       #include <unistd.h>
       void _exit(int status);

(2)异同:执行到exit()和_exit()都是无条件退出进程,但exit()退出前会处理IO缓冲区,以此保证数据完整性!但_exit()则不会,拍拍屁股就走人了,就像渣女一样,不管男方在其心里留的一颗泪,而我们的男方就是IO缓冲区被无情丢去!

4.wait()和waitpid()
(1)函数说明

NAME
       wait, waitpid - wait for a child process to stop or terminate

SYNOPSIS
       #include <sys/wait.h>

       pid_t wait(int *stat_loc);
       pid_t waitpid(pid_t pid, int *stat_loc, int options);

DESCRIPTION
    wait()和waitpid()函数应获取与调用方的一个子进程相关的状态信息。各种选项允许为已终止或停止的子进程获取状态信息。如果两个或多个子进程的状态信息可用,则未指定报告其状态的顺序。
wait()函数应暂停调用线程的执行,直到调用进程的某个已终止子进程的状态信息可用,或者直到传递其操作是执行信号捕获函数或终止进程的信号为止。如果在等待同一进程终止的wait()或waitpid()中有多个线程挂起,则在目标进程终止时,应正好有一个线程返回进程状态。如果在调用wait()之前状态信息可用,则应立即返回。
如果pid参数为(pid_t)-1且options参数为0,则waitpid()函数应等效于wait()。否则,其行为应通过pid和options参数的值进行修改。
(纯粹百度机翻,建议自己看文档理解)

(2)异同:wait()可以看成是waitpid()加上各种参数后的特例,wait()会阻塞父进程直到子进程结束,而waitpid()并不一定!

5.编写守护进程(Deamon 进程)
(1)创建子进程,关闭父进程。打开系统日志服务 openlog
(2)在子进程中创建新会话 setsid(),从此每步都记录日志 syslog
(3)改变当前工作目录 chdir()
(4)重设文件的权限掩码 umask(0)
(5)关闭文件描述符
(6)编写自己的任务代码
(7)结束后关闭日志 closelog

一个简单例子:

/*! \brief 简单的守护进程
 *  \param void
 *
 *return void
 */
void DeamonProcessTest()
{

    pid_t result;
    pid_t sid;
    int fd;
    char* buff="我是Deamon进程";
//(1)创建子进程,关闭父进程。打开系统日志服务 openlog
    result=fork();
    if(-1==result)
    {
        perror("fork error\n");
        exit(1);
    }
    else if(result>0)
    {
        exit(0);
    }
    openlog("Deamon进程",LOG_PID,LOG_DAEMON);
//(2)在子进程中创建新会话 setsid(),从此每步都记录日志 syslog
    if((sid=setsid())<0)
    {
        syslog(LOG_ERR,"%s\n","setsid");
        exit(1);
    }
//(3)改变当前工作目录 chdir()
    if((sid=chdir("/"))<0)
    {
        syslog(LOG_ERR,"%s\n","chdir");
        exit(1);
    }
//(4)重设文件的权限掩码 umask(0)
    umask(0);
//(5)关闭文件描述符
    for(int i=0; i<getdtablesize(); i++)
    {
        close(i);
    }
//(6)编写自己的任务代码
    while(1)
    {
      if((fd=open("/temp/daemon.log",
                  O_CREAT|O_WRONLY|O_APPEND,0600))<0)
      {
        syslog(LOG_ERR,"%s\n","open");
        exit(1);
      }
      write(fd,buff,strlen(buff)+1);
      close(fd);
      sleep(5);
    }
//(7)结束后关闭日志 closelog
 closelog();
 exit(0);
}

在/var/log/中查看syslog文件:

。。。。。。。。。
May 28 03:45:13 ubuntu Deamon进程[18186]: open
。。。。。。。。。

6.无名管道的使用方法
(1)创建

NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <unistd.h>

       int pipe2(int pipefd[2], int flags);

DESCRIPTION
      pipe()创建一个pipe,一个可用于进程间通信的单向数据通道。数组pipefd用于返回两个引用管道末端的文件描述符。pipefd[0]表示管道的读取端。pipefd[1]是指管道的写端。数据写入
管道的写端由内核缓冲,直到从管道的读端读取为止。有关详细信息,请参见管道(7)。
如果标志为0,则pipe2()与pipe()相同。以下值可以按位或在标志中以获得不同的行为:
O_NONBLOCK在两个新的打开文件描述上设置O_NONBLOCK文件状态标志。使用此标志可节省对fcntl(2)的额外调用,以获得相同的结果。
OúCLOEXEC在两个新文件描述符上设置close on exec(FDúCLOEXEC)标志。请参阅open(2)中对同一标志的描述,了解这可能有用的原因。
				(来自百度机翻,哈哈哈哈哈😎)
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

(2)关闭:使用close()关闭文件描述符就可关闭指定端了。
简单测试:

/*! \brief 测试pipe
 *  \param void
 *
 *return void
 */
void processPipeTest()
{
    int pipe_fd[2];
    pid_t result;
    char buff[128]= {0};
    const char* data="我真的好爱你";
    int readNum=0,writeNum=0;
    int readSum=0;
    //1.首先创建无名管道
    if((pipe(pipe_fd))<0)
    {
        perror("pipe create error");
    }
    //2.然后创建子进程
    result=fork();
    if(-1==result)
    {
        perror("fork error");
        return;
    }
    else if(0==result)
    {
        //子进程:可以关闭写通道,只保留读通道
        close(pipe_fd[1]);
        while((readNum=read(pipe_fd[0],buff,sizeof(buff)))>0)
        {
            readSum+=readNum;
            printf("读到%d个字节:%s\n",readSum,buff);
            if(readSum>100)
            {
                printf("女方:不读了,我累了,我们之间是单工通讯根本不在同一个频道,\
                       在一起是不可能的\n");
                close(pipe_fd[0]);
                return;
            }
        } 
    }
    else
    {
        //父进程:可以关闭读通道,只保留写通道
        close(pipe_fd[0]);
        while(1)
        {
 
            writeNum+=write(pipe_fd[1],data,strlen(data));
            printf("写入%d个字节\n",writeNum);
            if(writeNum>101)
            {
                printf("男方:不想写了,我累了,算了,最后写一次吧,你会收到吗,\
                       坚持了那么多字节,你能回我一句话吗\n");
                write(pipe_fd[1],data,sizeof(data));
                close(pipe_fd[1]);
                return;
            }
            sleep(1); 
        }
    }
}

看一下小故事,哈哈😂

写入90个字节
读到90个字节:我真的好爱你
写入108个字节
男方:不想写了,我累了,算了,最后写一次吧,你会收到吗,                       坚持了那么多字节,你能回我一句话吗
读到112个字节:我真的好爱你我
女方:不读了,我累了,我们之间是单工通讯根本不在同一个频道,                       在一起是不可能的


7.有名管道的使用方法

(1)概要:有名管道(FIFO)的创建可以使用mkfifo()函数,该函数类似文件由open(操作,可以指定管道的路径和访问权限(用户也可以在命令行,"mknod <管道名>"来创建有名管道)。

在创建管道成功之后,就可以使用 open()、read()和 write()这些函数了与普通文件一样
对于为读而打开的管道可在open()中设置O_RDONLY,为写而打开的管道可在 open()中设置O_WRONLY。

对于读进程
缺省情况下,如果当前FIFO内没有数据,读进程将一直阻塞到有数据写入或是FIFO写端都被关闭。

对于写进程
只要 FIFO有空间,数据就可以被写入。若空间不足,写进程会阻塞到数据都写入为止。
(2)mkfifo()函数

NAME
       mkfifo - make a FIFO special file (a named pipe)

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

DESCRIPTION
       mkfifo() makes a FIFO special file with name pathname.  mode specifies the FIFO's permissions.  It is modified by the process's umask in the usual way: the
       permissions of the created file are (mode & ~umask).

       A FIFO special file is similar to a pipe, except that it is created in a different way.  Instead of being an anonymous communications channel, a FIFO  spe‐
       cial file is entered into the file system by calling mkfifo().

       Once  you  have  created a FIFO special file in this way, any process can open it for reading or writing, in the same way as an ordinary file.  However, it
       has to be open at both ends simultaneously before you can proceed to do any input or output operations on it.  Opening a FIFO for reading  normally  blocks
       until some other process opens the same FIFO for writing, and vice versa.  See fifo(7) for nonblocking handling of FIFO special files.
(懒得机翻了,反正自己看(❁´◡`❁)

简单测试一下,看二人的爱情能收官嘛,哈哈

/*! \brief 测试fifo有名管道
 *  \param void
 *
 *return void
 */
void processFifoTest()
{
    int fd_w,fd_r;
    pid_t result;
    char buff_man[128]= {0};
    char buff_female[128]= {0};
    const char* love_file="love";
    const char* man_str="我真的好爱你";
    const char* female_str="真的吗?";
    int readNum=0,writeNum=0;
    int readSum=0;
    //首先创建有名管道文件
    if(-1==access(love_file,F_OK))
    {
        if(mkfifo(love_file,0666)<0)
        {
            perror("fail to mkfifo");
            exit(-1);
        }
    }
    //然后创建子进程
    result=fork();
    if(-1==result)
    {
        perror("fork error");
        return;
    }
    else if(0==result)
    {
        //子进程:以读写方式打开有名管道
        if((fd_r=open(love_file,O_RDWR))<0)
        {
            perror("fail to open fifo");
            exit(-1);
        }
        while((readNum=read(fd_r,buff_female,sizeof(buff_female)))>0)
        {
            readSum+=readNum;
            printf("读到%d个字节:%s\n",readSum,buff_female);
            printf("女方:%s\n",female_str);
            if(readSum>100)
            {//全双工通信
                write(fd_r,"女方:好啊,我们在一起吧!\n",strlen("女方:好啊,我们在一起吧!\n"));
                return;
            }
        }
    }
    else
    {
        //父进程:以读写方式打开有名管道
        if((fd_w=open(love_file,O_RDWR))<0)
        {
            perror("fail to open fifo");
            exit(-1);
        }
        while(1)
        {
            writeNum+=write(fd_w,man_str,strlen(man_str));
            printf("写入%d个字节\n",writeNum);
            if(writeNum>101)
            {
                printf("男方:不想写了,我累了,算了,最后写一次吧,你会收到吗,\
                       坚持了那么多字节,你能回我一句话吗\n");
                write(fd_w,man_str,strlen(man_str));
                sleep(3);
                read(fd_w,buff_man,sizeof(buff_man));
                printf("%s\n",buff_man);
                close(fd_w);
                return;
            }
            sleep(1);
        }
    }
}

看来喜结连理了呢,哈哈😉

读到90个字节:我真的好爱你
女方:真的吗?
写入108个字节
男方:不想写了,我累了,算了,最后写一次吧,你会收到吗,                       坚持了那么多字节,你能回我一句话吗
读到126个字节:我真的好爱你我真的好爱你
女方:真的吗?
女方:好啊,我们在一起吧!


Process returned 0 (0x0)   execution time : 8.005 s
Press ENTER to continue.

多线程就放在后面和网络编程一起写吧,写的有点累了,扬帆,启航!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖工人_0803号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值