linux应用开发基础1
创始人
2025-05-28 02:50:46
0

fork()

pid_t fork(void);

fork() 创建一个新的子进程,fork() 之前的内容只在父进程中运行一次,之后的内容在父子进程之间都会运行。
在父进程中的 fork() 调用后返回的是新的子进程的 PID,子进程中的 fork() 函数调用后返回的是 0。
子进程与父进程一致的内容:
• 进程的地址空间。
• 进程上下文、代码段。
• 进程堆空间、栈空间,内存信息。
• 进程的环境变量。
• 标准 IO 的缓冲区。
• 打开的文件描述符。
• 信号响应函数。
• 当前工作路径
子进程独有的内容:
• 进程号 PID。 PID 是身份证号码,是进程的唯一标识符
• 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
• 挂起的信号。这些信号是已经响应但尚未处理的信号,也就是”悬挂”的信号,子进程也不会继承这些信号

#include 
#include 
#include 
#include 
int main(void)
{pid_t result;printf("This is a fork demo!\n\n");/*调用 fork()函数*/result = fork();/*通过 result 的值来判断 fork()函数的返回情况,首先进行出错处理*/if(result == -1) {printf("Fork error\n");}/*返回值为 0 代表子进程*/else if (result == 0) {printf("The returned value is %d, In child process!! My PID is %d\n\n", result, getpid());}/*返回值大于 0 代表父进程*/else {printf("The returned value is %d, In father process!! My PID is %d\n\n", result, getpid());}return result;
}

exec 系列函数

/*** exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或* 目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进* 程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也* 可以是 Linux 下任何可执行的脚本文件。* * 在 Linux 中使用 exec 函数族主要有两种情况。*    当进程认为自己不能再为系统和用户做出任何贡献时, 就可以调用 exec 函数族中的任意一个函数让自己重生。*    如果一个进程想执行另一个程序,那么它就可以调用 fork()函数新建一个进程,然后调用 exec 函*     数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程(这种情况非常普遍)。 * */
//返回-1表示错误
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 execve(const char *path, char *const argv[], char *const envp[])/*execl()函数用于执行参数path字符串所代表的文件路径(必须指定路径),接下来是一系列可变参数,它们代表执行该文件时传递过去的 ``argv[0]、argv[1]… argv[n]`` ,最后一个参数必须用空指针NULL作为结束的标志。*/err = execl("/bin/ls", "ls", "-la", NULL);/*与execl的差异是,execlp()函数会从PATH环境变量所指的目录中查找符合参数file的文件名(不需要指定完整路径)。*/err = execlp("ls", "ls", "-la", NULL);/*与execl的差异是,execle()函数会通过最后一个参数(envp)指定新程序使用的环境变量。*/char *envp[] = {"/bin", NULL};err = execle("/bin/ls", "ls", "-la", NULL, envp);/*与execl的差异是,直接使用数组来装载要传递给子程序的参数/   */char *argv[] = {"ls", "-la", NULL};err = execv("/bin/ls", argv);/*是execlp,execv函数的结合体*/char *argv[] = {"ls", "-la", NULL};err = execvp("ls", argv);/*是execle,execv函数的结合体*/char *argv[] = {"ls", "-la", NULL};char *envp[] = {"/bin", NULL};err = execve("/bin/ls", argv, envp);

终止进程

_exit() :直接通过系统调用使进程终止运行,当然,在终止进程的时候会清除这个进程使用的内存空间,并销毁它在内核中的各种数据结构;
exit() : 调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,这就是“清除 I/O 缓冲”

void _exit(int status);
void exit(int status);

wait() 函数

pid_t wait(int *wstatus);

• wait() 要与 fork() 配套出现,如果在使用 fork() 之前调用 wait(), wait() 的返回值则为-1,正常情况下 wait() 的返回值为子进程的 PID。
• 参数 wstatus 用来保存被收集进程退出时的一些状态,它是一个指向 int 类型的指针,但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样做),我们就可以设定这个参数为 NULL。
Linux 系统提供了关于等待子进程退出状态的一些宏定义,我们可以使用这些宏定义来直接判断子进程退出的状态:
• WIFEXITED(status) :如果子进程正常结束,返回一个非零值
• WEXITSTATUS(status):如果 WIFEXITED 非零,返回子进程退出码
• WIFSIGNALED(status) :子进程因为捕获信号而终止,返回非零值
• WTERMSIG(status) :如果 WIFSIGNALED 非零,返回信号代码
• WIFSTOPPED(status):如果子进程被暂停,返回一个非零值
• WSTOPSIG(status):如果 WIFSTOPPED 非零,返回一个信号代码

/* 调用 wait,父进程阻塞 */
child_pid = wait(&status); //
if(child_pid == pid)
{if(WIFEXITED(status))  //判断是否正常结束{child_retcode = WEXITSTATUS(status); //WIFEXITED 非零,获取子进程退出码}
}

匿名(无名)管道

只能在父子进程中使用,父进程在产生子进程前必须打开一个管道文件,然后 fork 产生子进程,这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到使用同一个管道通信的目的。此时除了父子进程外,没人知道这个管道文件的描述符,所以通过这个管道中的信息无法传递给其他进程。

/** pipe.c *//*** ps –ef | grep ntp* * 进程 ps -ef              进程 grep ntp*          |              |*          |---- pipe ----|* * 管道是 Linux 中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有如下特点:*   它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。*   它是一个半双工的通信模式,具有固定的读端和写端。*   管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read()和 write()等函数。*    但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中。* • 写入操作不具有原子性,因此只能用于一对一的简单通信情形。* * 管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符 fds[0]和 fds[1],* 其中 fds[0]固定用于读管道,而 fd[1]固定用于写管道,这样就构成了一个半双工的通道。* 管道关闭时只需将这两个文件描述符关闭即可,可使用普通的 close()函数逐个关闭各个文件描述符。* *           用户进程*  fd[0]               fd[1]*    |                   |*    读                  写*    |--------管道--------|* *     父进程                 子进程*  fd[0]fd[1]            fd[0]fd[1] *      |                     |*     读写                   读写*      |---------管道---------|* * 函数原型:int pipe(int fd[2])* *//*** 注意点:*   只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的 SIGPIPE 信号(通常为 Broken pipe 错误)。*   向管道写入数据时, Linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。*   父子进程在运行时,它们的先后次序并不能保证,建议使用进程之间的同步与互斥机制之后。*/#include 
#include 
#include 
#include 
#include 
#include 
#include #define MAX_DATA_LEN 256
#define DELAY_TIME 1int main()
{pid_t pid;int pipe_fd[2];char buf[MAX_DATA_LEN];const char data[] = "Pipe Test Program";int real_read, real_write;memset((void*)buf, 0, sizeof(buf));/* 创建管道 */if (pipe(pipe_fd) < 0){printf("pipe create error\n");exit(1);}/* 创建一子进程 */if ((pid = fork()) == 0){/* 子进程关闭写描述符,并通过使子进程暂停 3s 等待父进程已关闭相应的读描述符 */close(pipe_fd[1]);//sleep(DELAY_TIME * 3);/* 子进程读取管道内容 阻塞读取, read() 函数读取一个关闭了写描述符的管道,那
么 read() 会返回 0*/if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0){printf("%d bytes read from the pipe is '%s'\n", real_read, buf);}/* 关闭子进程读描述符 */close(pipe_fd[0]);exit(0);}else if (pid > 0){/* 父进程关闭读描述符,并通过使父进程暂停 1s 等待子进程已关闭相应的写描述符 */close(pipe_fd[0]);sleep(DELAY_TIME);if((real_write = write(pipe_fd[1], data, strlen(data))) != -1){printf("Parent write %d bytes : '%s'\n", real_write, data);}/*关闭父进程写描述符*/close(pipe_fd[1]);/*收集子进程退出信息*/waitpid(pid, NULL, 0);exit(0);}
}

命名(有名)管道FIFO

命名管道不同于无名管道之处在于它提供了一个路径名与之关联,以一个文件形式存在于文件系统中,这样,即使与命名管道的创建进程不存在“血缘关系”的进程,只要可以访问该命名管道文件的路径,就能够彼此通过命名管道相互通信,因为可以通过文件的形式,那么就可以调用系统中对文件的操作,如打开(open)、读(read)、写(write)、关闭(close)等函数,虽然命名管道文件存储在文件系统中,但数据却是存在于内存中的,这点要区分开。
命名管道有以下的特征:
• 有名字,存储于普通文件系统之中。
• 任何具有相应权限的进程都可以使用 open() 来获取命名管道的文件描述符。
• 跟普通文件一样:使用统一的 read()/write() 来读写。
• 跟普通文件不同:不能使用 lseek() 来定位,原因是数据存储于内存中。
• 具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏。
• 遵循先进先出(First In First Out)原则,最先被写入 FIFO 的数据,最先被读出来。
命令创建命名管道:

# 执行如下命令
mkfifo test
file test
# 以下是命令输出,可以看出它是一个命名管道类型的文件
test: fifo (named pipe)

函数创建命名管道

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

mode 模式及权限参数说明:
• O_RDONLY:读管道。
• O_WRONLY:写管道。
• O_RDWR:读写管道。
• O_NONBLOCK:非阻塞。
• O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三个参数为其设置权限。
• O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在。
函数返回值说明如下:
• 0:成功
• EACCESS:参数 filename 所指定的目录路径无可执行的权限。
• EEXIST:参数 filename 所指定的文件已存在。
• ENAMETOOLONG:参数 filename 的路径名称太长。
• ENOENT:参数 filename 包含的目录不存在。
• ENOSPC:文件系统的剩余空间不足。
• ENOTDIR:参数 filename 路径中的目录存在但却非真正的目录。
• EROFS:参数 filename 指定的文件存在于只读文件系统内。

使用 FIFO 的过程中,当一个进程对管道进行读操作时:
• 若该管道是阻塞类型,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞到有数据写入。
• 若该管道是非阻塞类型,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。即如果FIFO 内没有数据,读函数将立刻返回 0。
使用 FIFO 的过程中,当一个进程对管道进行写操作时:
• 若该管道是阻塞类型,则写操作将一直阻塞到数据可以被写入。
• 若该管道是非阻塞类型而不能写入全部数据,则写操作进行部分写入或者调用失败

/**fifo.c *//*** 无名管道,它只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限制, 它可以使互不相关的两个进程实现彼此通信。 * 该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。* 不过 FIFO 是严格地遵循先进先出规则的,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如 lseek()等文件定位操作。* 有名管道的创建可以使用函数 mkfifo(),该函数类似文件中的 open()操作,可以指定管道的路径和打开的模式* * 在创建管道成功之后,就可以使用 open()、 read()和 write()这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在 open()中设置 O_RDONLY,* 对于为写而打开的管道可在 open()中设置 O_WRONLY,在这里与普通文件不同的是阻塞问题。由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,* 这里的非阻塞标志可以在 open()函数中设定为 O_NONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行讨论。* (1)对于读进程。*       若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞到有数据写入。*       若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。即如果 FIFO内没有数据,则读函数将立刻返回 0。* (2)对于写进程。*       若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。*       若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败* * 函数原型 int mkfifo(const char *filename,mode_t mode)* 函数传入值 filename:要创建的管道名字(包含路径)* 函数传入值 mode:*       O_RDONLY:读管道*       O_WRONLY:写管道*       O_RDWR:读写管道*       O_NONBLOCK:非阻塞*       O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三个参数为其设置权限*       O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在* 函数返回值:*       0:成功*       EACCESS:参数 filename 所指定的目录路径无可执行的权限*       EEXIST:参数 filename 所指定的文件已存在*       ENAMETOOLONG:参数 filename 的路径名称太长*       ENOENT:参数 filename 包含的目录不存在*       ENOSPC:文件系统的剩余空间不足*       ENOTDIR:参数 filename 路径中的目录存在但却非真正的目录*       EROFS:参数 filename 指定的文件存在于只读文件系统内*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #define MYFIFO "myfifo"    /* 有名管道文件名*/#define MAX_BUFFER_SIZE PIPE_BUF /* 4096 定义在于 limits.h 中*/void fifo_read(void)
{char buff[MAX_BUFFER_SIZE];int fd;int nread;printf("***************** read fifo ************************\n");/* 判断有名管道是否已存在,若尚未创建,则以相应的权限创建*/if (access(MYFIFO, F_OK) == -1){if ((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST)){printf("Cannot create fifo file\n");exit(1);}}/* 以只读阻塞方式打开有名管道 */fd = open(MYFIFO, O_RDONLY);if (fd == -1){printf("Open fifo file error\n");exit(1);}memset(buff, 0, sizeof(buff));if ((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0){printf("Read '%s' from FIFO\n", buff);}printf("***************** close fifo ************************\n");close(fd);exit(0);
}void write_fifo(void)
{int fd;char buff[] = "this is a fifo test demo";int nwrite;sleep(2);   //等待子进程先运行/* 以只写阻塞方式打开 FIFO 管道 */fd = open(MYFIFO, O_WRONLY | O_CREAT, 0644);if (fd == -1){printf("Open fifo file error\n");exit(1);}printf("Write '%s' to FIFO\n", buff);/*向管道中写入字符串*/nwrite = write(fd, buff, MAX_BUFFER_SIZE);if(wait(NULL))  //等待子进程退出{close(fd);exit(0);}}int main()
{pid_t result;/*调用 fork()函数*/result = fork();/*通过 result 的值来判断 fork()函数的返回情况,首先进行出错处理*/if(result == -1){printf("Fork error\n");}else if (result == 0) /*返回值为 0 代表子进程*/{fifo_read();}else /*返回值大于 0 代表父进程*/{write_fifo();}return result;
}

相关内容

热门资讯

仿安卓4系统下载,下载与体验全... 你有没有想过,手机系统就像是我们生活的操作系统,有时候换一个新系统,就像是给生活来个大升级呢!今天,...
安卓手机的系统日志,探寻系统运... 你有没有发现,每次你的安卓手机出了点小状况,比如突然卡顿或者电池耗得飞快,你都会想探究个究竟?别急,...
安卓系统azw3,Androi... 你有没有发现,手机里的安卓系统越来越强大了?今天,就让我带你深入了解一下这个神奇的系统,尤其是那个神...
智能安卓电视系统卡,智能安卓电... 你有没有遇到过这种情况?家里的智能安卓电视系统突然卡住了,屏幕上那个熟悉的界面就像被施了魔法一样,怎...
电脑虚拟安卓系统教程,教程全解... 你有没有想过,让你的电脑也能像手机一样,随时随地玩各种安卓应用?没错,这就是今天我要跟你分享的神奇魔...
qq飞车分安卓系统,QQ飞车安... 你有没有发现,最近QQ飞车这款游戏在安卓系统上可是火得一塌糊涂啊!不管是街头巷尾,还是朋友圈里,都能...
淘手游苹果系统安卓系统,苹果系... 你有没有发现,现在手机游戏越来越火了?不管是走在街头,还是坐在家里,总能看到大家拿着手机,眼睛一眨不...
安卓系统定位app华为,守护您... 你有没有发现,现在手机里的APP真是五花八门,各有各的用处。今天,咱们就来聊聊安卓系统里一个特别实用...
安卓系统显示矫准,打造清晰视觉... 你有没有发现,你的安卓手机屏幕有时候显示得有点歪歪扭扭的?别急,这可不是什么大问题,今天就来给你详细...
安卓系统服务有病毒,病毒生成背... 你知道吗?最近在安卓系统上,服务里竟然悄悄潜入了病毒!这可不是闹着玩的,得赶紧来聊聊这个事儿,让你了...
解决ios系统和安卓系统游戏,... 你是不是也和我一样,手机里装了各种游戏,却因为iOS和安卓系统不兼容而头疼不已?别急,今天就来给你支...
安卓系统浮窗app,便捷多任务... 你有没有发现,手机上的那些小窗口,就像魔法一样,让我们的使用体验瞬间升级?没错,说的就是安卓系统里的...
安卓手工刷谷歌系统,体验原生魅... 你有没有想过,你的安卓手机其实可以焕发第二春呢?没错,就是通过手工刷谷歌系统,让你的手机体验焕然一新...
调整安卓系统时间流速,揭秘安卓... 你有没有发现,时间有时候就像那调皮的小精灵,在我们不经意间溜走?有时候,我们希望时间能慢一些,让生活...
网易云游戏安卓系统,解锁全新游... 亲爱的游戏迷们,你是不是也和我一样,对手机游戏情有独钟?今天,我要和你聊聊一个特别酷的话题——网易云...
安卓系统那个优化最好,探索最佳... 你有没有发现,手机里的安卓系统就像是个调皮的小家伙,总是时不时地给你点小麻烦?不过别担心,今天咱们就...
安卓手机安装windous系统... 你有没有想过,你的安卓手机也能装上Windows系统?是的,你没听错,就是那个曾经陪伴我们无数个日夜...
华为手机适合安卓系统,安卓生态... 你有没有发现,最近华为手机在安卓系统圈子里可是风头无两呢?这不,我就来给你好好捋一捋,为什么华为手机...
安卓系统下载福建助学,安卓系统... 你有没有听说最近安卓系统上有个超级棒的福建助学项目?没错,就是那个能让你轻松下载各种学习资源的神器!...
i7安卓系统,引领智能科技新潮... 你有没有想过,手机和电脑的结合体是什么样的呢?想象一个既能流畅运行大型游戏,又能轻松处理日常办公的设...