Linux C语言编程学习笔记 (1)进程控制入门
想进行Linux系统开发已经很久了,一直没有付诸实践。今日终于开始学习Linux下的C语言编程,研究一天,终于大概弄明白了Linux系统进程管理的一些基本概念和编程方法,总结下来以方便大家学习和自己实践。
进程系统
Linux是个多任务多用户的操作系统,系统直接管理的每个任务的最小单位,就是进程(process)。每个进程都有一个惟一的标识符pid,不同的进程pid不相同,在Shell下输入ps -A,可以显示当前的所有进程。一个进程不代表一个应用程序(application),因为一个应用程序可能对应多个进程,也不代表一个可执行文件(executable
file),因为一些可执行文件可以被同时运行多个,它们是互不相干的。
在Linux中,进程不是相互独立的,每个进程(除了init进程)都有一个父进程(parent process),同时每个进程可以有0个1个或多个子进程(child process)。换句话说,Linux的进程是一个树形结构,在Shell下输入pstree可以查看这个树的形状。下图为pstree返回结果的一部分。
init─┬─NetworkManager─┬─dhclient
│ └─{NetworkManager}
├─SystemToolsBack
├─avahi-daemon───avahi-daemon
├─bonobo-activati───{bonobo-activati}
├─console-kit-dae───63*[{console-kit-dae}]
├─hald───hald-runner─┬─hald-addon-acpi
│ ├─hald-addon-cpuf
├─pulseaudio─┬─gconf-helper
│ └─2*[{pulseaudio}]
├─rsyslogd───2*[{rsyslogd}]
├─seahorse-daemon
├─telepathy-gabbl
├─telepathy-haze─┬─telepathy-haze
│ └─{telepathy-haze}
├─trashapplet
└─wpa_supplicant
在C语言中,获得当前进程的pid的函数是pid_t getpid(void);,获得当前进程的父进程的pid的函数是pid_t getppid(void);,两者都在unistd.h中声明。
用户和权限
因为Linux是多用户的系统,所以内核中有着强大的用户控制,因此每个进程还有一个所有者,即实际用户ID(uid)。系统uid是一个整数,不同于用户名。默认情况下进程的uid继承于父进程。例如我用所有者为byvoid(uid为1000)的bash终端启动了一个进程,那么这个进程的uid也是1000。用户uid可以通过uid_t getuid(void);函数获得。如果权限满足,程序在运行时可以修改uid,C语言函数为int
setuid(uid_t uid);,如果成功执行返回0,否则返回-1。只有具有root用户权限的进程可以设置uid。
除此以外,进程还有一个有效用户ID(euid)。euid是决定进程文件系统权限的身份,一般情况下进程euid和uid是相同的。在C语言中可以通过uid_t geteuid(void);函数获得进程euid。同样euid也可以修改,函数为int seteuid(uid_t uid);仅当当前uid和euid中至少有一个为0(root)时,才可以设置euid。有一种特殊情况,就是一个二进制可执行文件所有者为root,并且被chmod
+s后,在一般用户身份下执行,这时产生的进程uid为一般用户,而euid为0(root),这种情况下该进程具有和root一样高的权限。
进程生成
fork函数
Linux允许用户创建用户进程的子进程,在C语言中通过pid_t fork(void);函数实现。fork函数的基本功能是生成一个子进程,并复制当前进程的数据段和堆栈段,子进程和父进程共用代码段。因为复制了堆栈段,所以父进程和子进程都停留在fork函数的栈帧中,fork函数要返回两次,一次在父进程中返回,一次在子进程中返回,但是两次的返回值是不一样的。在父进程中,fork函数返回值为子进程的pid(如果成功调用的话),在子进程中,fork函数的返回值为0。因此可以根据返回值的不同确定程序的运行流程。父进程和子进程默认情况下是同步执行的,由系统内核调度,哪个先执行是未知的。因为父子进程的数据段和堆栈段都是独立的,所以两者互不干涉,各行其是,内存不能直接共享。
执行程序
Linux中要执行一个外部程序,必须生成一个子进程,因为内核执行程序的命令exec会替换掉当前进程的地址空间的所有内容并继续执行,执行另一个程序意味着当前程序不再执行。在C语言中,并没有exec这样的一个函数,而是有下列一组函数。
int execl (const char * file,const char * arg,...);
int execlp(const char * file,const char * arg,...);
int execle(const char * file,const char * arg,...,NULL,char * const envp[ ]);
int execv (const char * file, char * const argv[ ]);
int execvp(const char *file ,char * const argv []);
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
其中以execl开头的函数,第一个参数file为可执行文件名,接下来有若干个参数,分别为传入的argv[0],argv[1],argv[2],...,最有以NULL结束。如果file参数为路径名(其中包含'/'),execl函数会直接定位到文件并执行,否则仅在当前目录下寻找文件,而execlp函数遇到文件名则会按照环境变量PATH的顺序寻找。execle最后一个参数为二维字符数组,表示传递给程序新的环境变量列表。execv,execvp,execve和前三者用法相似,只不过不以可变参数列表的方式传递参数,换以二维字符数组。上述函数执行失败后会返回-1,如果执行成功的话将会不返回,因为代码段已经被新的可执行程序替换。
进程阻塞
wait函数
在实际的应用中,有时候需要让父进程停下来等待子进程的执行完毕,这时候就需要进行进程阻塞(process blocking)。C语言中使用pid_t wait(int *statloc)函数可以得到子进程的结束信息。调用wait函数的进程会阻塞,直到该进程的任意一个子进程结束,wait函数会返回结束的子进程的pid,结束信息保存在statloc指针指向的内存区域。如果该进程没有活动的子进程,那么立即出错并返回-1。如果statloc指针为NULL,那么表示不关心进程结束的状态。如果有多个子进程,wait函数返回哪个数不确定的,需要通过pid来判断。
如果我们需要等待特定的一个进程,可以使用pid_t waitpid(pid_t pid,int *statloc,int options)函数。waitpid函数的第一个参数指定了要等待的进程pid,并且有更多的选项。
僵尸进程
当一个子进程退出时,如果没有被父进程通过wait取得状态信息,这些信息会一直保留在内核内存中,子进程的pid也不会被消除,直到父进程退出,这时候这些子进程被称为僵尸进程(zombie process)。虽然僵尸进程只占用很少的一点内存,但如果是长期运行的服务器,积累大量的僵尸进程会导致系统进程表被塞满,以至于无法创建新的进程。产生一个僵尸进程很容易,只需要让子进程先于父进程退出即可,在父进程退出之前,子进程将会成为僵尸进程。
孤儿进程
与僵尸进程相反,如果父进程没有阻塞并先于子进程退出,那么子进程将会成为孤儿进程(orphan process)。Linux系统中init进程负责领养所有孤儿进程,也就是说,孤儿进程的父进程会被设为init进程。init进程作为系统守护进程(daemon process),会不断调用wait函数等待领养的孤儿进程退出,不会产生僵尸进程。
利用孤儿进程避免僵尸进程
许多时候我们不能让父进程阻塞下来等待子进程处理完以后再继续,例如在多用户的服务器程序上。这时如果让子进程处理事务,就会产生大量僵尸进程。避免僵尸进程出现的一个经典方法就是利用孤儿进程,具体方法为首先用父进程产生一个子进程,然后让子进程立刻产生一个孙子进程,用孙子进程来处理事务。同时父进程阻塞等待,并让子进程则立刻退出。这时候由于子进程已经退出,孙子进程就变成了孤儿进程,被init领养。而子进程立刻退出后,父进程收到信号并正确销毁了子进程,相比之下父进程只阻塞了可以忽略不计的一瞬间。下面程序是一个例子避免僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
if(fork() == 0) //启动一个子进程
{
printf("the child\n");
if(fork() == 0) //启动一个孙子进程
{
printf("do something you want\n");
sleep(5);
printf("done\n");
exit(0);
}
else //子进程立刻退出
exit(0);
}
else
{ //父进程立即阻塞
wait(NULL);
printf("the parent\n");
sleep(10);
}
return 0;
}
Linux C语言编程学习笔记 (1)进程控制入门的更多相关文章
- linux下的c语言编程学习笔记
视频参看csdn学院王阳和下面的linux环境下c语言编程基础相当的经典,其中王阳的视频讲的很好,相当的经典 编译hellogcc.c需要依赖/home目录下的头文件 为了避免同一个文件被includ ...
- LInux下socket编程学习笔记
1.socket套接字: socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模 ...
- Linux C网络编程学习笔记
Linux C网络编程总结报告 一.Linux C 网络编程知识介绍: 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端:(client) 在网络程序中, ...
- 【C语言编程学习笔记】利用462字节代码实现雅虎logo ACSII 动画!
ACSII 动画演示: 不过本文介绍的是另一个作品:c 代码实现雅虎 logo ACSII 动图. 运行后,你将会看到: 它是一个 20fps.抗锯齿的 Yahoo! logo ASCII 动 ...
- Linux Shell脚本编程学习笔记和实战
http://www.1987.name/141.html shell基础 终端打印.算术运算.经常使用变量 Linux下搜索指定文件夹下特定字符串并高亮显示匹配关键词 从键盘或文件里获取标准输入 [ ...
- linux学习笔记-13.进程控制
1.查看用户最近登录情况 lastlastlog 2.查看硬盘使用情况 df 3.查看文件大小 du 4.查看内存使用情况 free 5.查看文件系统 /proc 6.查看日志 ls /var/log ...
- Perl语言编程>>学习笔记2
1. Perl中变量的常用表示 ${var} 相当于 $var $Dog::days 在Dog包里面的变量$days $#days @days 的最后一个索引 ] $days 引用的数组 ...
- Perl语言编程>>学习笔记
1. 使用反引号可以调用外部程序并返回程序的输出, 如 $cwd = `pwd`; 2. Perl 中的变量类型之间的区别主要是单数和复数; 单数变量称为标量 $scalar , 复数变量称为数组 ...
- APUE 学习笔记(六) 进程控制
1. fork 创建新进程 fork创建的新进程称为子进程,fork函数调用一次,返回两次. 两次返回的唯一区别就是子进程的返回值是0,而父进程的返回值是新子进程的进程ID 在fork之后是父进程先执 ...
随机推荐
- python创建与遍历List二维列表
python创建与遍历List二维列表 觉得有用的话,欢迎一起讨论相互学习~Follow Me python 创建List二维列表 lists = [[] for i in range(3)] # 创 ...
- python---django中orm的使用(1)
首先推荐两篇文章:Django之ORM操作,http://www.cnblogs.com/yuanchenqi/articles/6083427.html十分全面. 另外和python---ORM之S ...
- git安装与初始化
命令行 Git有多重方式使用 原生命令行,才能使用git所有命令,会git命令再去用gui图形工具,完全无压力 GUI图形软件,只是实现了git的部分功能,以减免操作难度,难以记住git原生命令 不同 ...
- js实现表单提交submit(),onsubmit
通常表单的提交有两种方式,一是直接通过html的form提交,代码如下: <form action="" method="" id="forms ...
- [转载]AngularJS 开发者最常犯的 10 个错误
http://www.oschina.net/translate/top-10-mistakes-angularjs-developers-make
- js星星评分插件
下载:https://files.cnblogs.com/files/wordblog/%E6%98%9F%E6%98%9F%E6%8F%92%E4%BB%B6.rar
- Jenkins的安装及使用(二)
介绍两个方面:编译本地项目和拉取git代码并编译 在这之前,先要进行一个配置. 一.编译本地项目 开始添加任务,任务类型选择自由风格: 点击项目进入详情,源码管理选择无 在构建的地方选择项目,然后注意 ...
- MVC常用特性使用
简介 在以前的文章中,我和大家讨论如何用SingalR和数据库通知来完成一个消息监控应用. 在上一篇文章中,我介绍了如何在MVC中对MongoDB进行CRUD操作. 今天,我将继续介绍一些在开发中非常 ...
- RTS与CTS的含义【转】
转自:http://www.cnblogs.com/sunyubo/archive/2010/04/21/2282176.html 一.RS232标准中的RTS与CTS RTS,CTS------请求 ...
- Linux 串口、usb转串口驱动分析(2-1) 【转】
转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186851 Linux 串口.usb转 ...