aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric W. Biederman <ebiederm@xmission.com>2006-06-26 03:25:50 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2006-06-26 12:58:25 -0400
commit0bc58a910220be3446eedc8e77fd45c0a16d8f25 (patch)
tree70ffacd6e2e22471034b053e441f305a9507a052
parentcd6a3ce9ec040c0b56ea92a81ff710417798c559 (diff)
[PATCH] proc: refactor reading directories of tasks
There are a couple of problems this patch addresses. - /proc/<tgid>/task currently does not work correctly if you stop reading in the middle of a directory. - /proc/ currently requires a full pass through the task list with the tasklist lock held, to determine there are no more processes to read. - The hand rolled integer to string conversion does not properly running out of buffer space. - We seem to be batching reading of pids from the tasklist without reason, and complicating the logic of the code. This patch addresses that by changing how tasks are processed. A first_<task_type> function is built that handles restarts, and a next_<task_type> function is built that just advances to the next task. first_<task_type> when it detects a restart usually uses find_task_by_pid. If that doesn't work because there has been a seek on the directory, or we have already given a complete directory listing, it first checks the number tasks of that type, and only if we are under that count does it walk through all of the tasks to find the one we are interested in. The code that fills in the directory is simpler because there is only a single for loop. The hand rolled integer to string conversion is replaced by snprintf which should handle the the out of buffer case correctly. Signed-off-by: Eric W. Biederman <ebiederm@xmission.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--fs/proc/base.c268
1 files changed, 163 insertions, 105 deletions
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 98eaeaa9fdd1..2236f7d3878e 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1545,8 +1545,6 @@ static struct file_operations proc_tgid_attr_operations;
1545static struct inode_operations proc_tgid_attr_inode_operations; 1545static struct inode_operations proc_tgid_attr_inode_operations;
1546#endif 1546#endif
1547 1547
1548static int get_tid_list(int index, unsigned int *tids, struct inode *dir);
1549
1550/* SMP-safe */ 1548/* SMP-safe */
1551static struct dentry *proc_pident_lookup(struct inode *dir, 1549static struct dentry *proc_pident_lookup(struct inode *dir,
1552 struct dentry *dentry, 1550 struct dentry *dentry,
@@ -2029,88 +2027,83 @@ out:
2029} 2027}
2030 2028
2031#define PROC_NUMBUF 10 2029#define PROC_NUMBUF 10
2032#define PROC_MAXPIDS 20
2033 2030
2034/* 2031/*
2035 * Get a few tgid's to return for filldir - we need to hold the 2032 * Find the first tgid to return to user space.
2036 * tasklist lock while doing this, and we must release it before 2033 *
2037 * we actually do the filldir itself, so we use a temp buffer.. 2034 * Usually this is just whatever follows &init_task, but if the users
2035 * buffer was too small to hold the full list or there was a seek into
2036 * the middle of the directory we have more work to do.
2037 *
2038 * In the case of a short read we start with find_task_by_pid.
2039 *
2040 * In the case of a seek we start with &init_task and walk nr
2041 * threads past it.
2038 */ 2042 */
2039static int get_tgid_list(int index, unsigned long version, unsigned int *tgids) 2043static struct task_struct *first_tgid(int tgid, int nr)
2040{ 2044{
2041 struct task_struct *p; 2045 struct task_struct *pos = NULL;
2042 int nr_tgids = 0;
2043
2044 index--;
2045 read_lock(&tasklist_lock); 2046 read_lock(&tasklist_lock);
2046 p = NULL; 2047 if (tgid && nr) {
2047 if (version) { 2048 pos = find_task_by_pid(tgid);
2048 p = find_task_by_pid(version); 2049 if (pos && !thread_group_leader(pos))
2049 if (p && !thread_group_leader(p)) 2050 pos = NULL;
2050 p = NULL; 2051 if (pos)
2052 nr = 0;
2051 } 2053 }
2054 /* If nr exceeds the number of processes get out quickly */
2055 if (nr && nr >= nr_processes())
2056 goto done;
2052 2057
2053 if (p) 2058 /* If we haven't found our starting place yet start with
2054 index = 0; 2059 * the init_task and walk nr tasks forward.
2055 else 2060 */
2056 p = next_task(&init_task); 2061 if (!pos && (nr >= 0))
2062 pos = next_task(&init_task);
2057 2063
2058 for ( ; p != &init_task; p = next_task(p)) { 2064 for (; pos && pid_alive(pos); pos = next_task(pos)) {
2059 int tgid = p->pid; 2065 if (--nr > 0)
2060 if (!pid_alive(p))
2061 continue;
2062 if (--index >= 0)
2063 continue; 2066 continue;
2064 tgids[nr_tgids] = tgid; 2067 get_task_struct(pos);
2065 nr_tgids++; 2068 goto done;
2066 if (nr_tgids >= PROC_MAXPIDS)
2067 break;
2068 } 2069 }
2070 pos = NULL;
2071done:
2069 read_unlock(&tasklist_lock); 2072 read_unlock(&tasklist_lock);
2070 return nr_tgids; 2073 return pos;
2071} 2074}
2072 2075
2073/* 2076/*
2074 * Get a few tid's to return for filldir - we need to hold the 2077 * Find the next task in the task list.
2075 * tasklist lock while doing this, and we must release it before 2078 * Return NULL if we loop or there is any error.
2076 * we actually do the filldir itself, so we use a temp buffer.. 2079 *
2080 * The reference to the input task_struct is released.
2077 */ 2081 */
2078static int get_tid_list(int index, unsigned int *tids, struct inode *dir) 2082static struct task_struct *next_tgid(struct task_struct *start)
2079{ 2083{
2080 struct task_struct *leader_task = proc_task(dir); 2084 struct task_struct *pos;
2081 struct task_struct *task = leader_task;
2082 int nr_tids = 0;
2083
2084 index -= 2;
2085 read_lock(&tasklist_lock); 2085 read_lock(&tasklist_lock);
2086 /* 2086 pos = start;
2087 * The starting point task (leader_task) might be an already 2087 if (pid_alive(start))
2088 * unlinked task, which cannot be used to access the task-list 2088 pos = next_task(start);
2089 * via next_thread(). 2089 if (pid_alive(pos) && (pos != &init_task)) {
2090 */ 2090 get_task_struct(pos);
2091 if (pid_alive(task)) do { 2091 goto done;
2092 int tid = task->pid; 2092 }
2093 2093 pos = NULL;
2094 if (--index >= 0) 2094done:
2095 continue;
2096 if (tids != NULL)
2097 tids[nr_tids] = tid;
2098 nr_tids++;
2099 if (nr_tids >= PROC_MAXPIDS)
2100 break;
2101 } while ((task = next_thread(task)) != leader_task);
2102 read_unlock(&tasklist_lock); 2095 read_unlock(&tasklist_lock);
2103 return nr_tids; 2096 put_task_struct(start);
2097 return pos;
2104} 2098}
2105 2099
2106/* for the /proc/ directory itself, after non-process stuff has been done */ 2100/* for the /proc/ directory itself, after non-process stuff has been done */
2107int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir) 2101int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
2108{ 2102{
2109 unsigned int tgid_array[PROC_MAXPIDS];
2110 char buf[PROC_NUMBUF]; 2103 char buf[PROC_NUMBUF];
2111 unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY; 2104 unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY;
2112 unsigned int nr_tgids, i; 2105 struct task_struct *task;
2113 int next_tgid; 2106 int tgid;
2114 2107
2115 if (!nr) { 2108 if (!nr) {
2116 ino_t ino = fake_ino(0,PROC_TGID_INO); 2109 ino_t ino = fake_ino(0,PROC_TGID_INO);
@@ -2119,62 +2112,123 @@ int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
2119 filp->f_pos++; 2112 filp->f_pos++;
2120 nr++; 2113 nr++;
2121 } 2114 }
2115 nr -= 1;
2122 2116
2123 /* f_version caches the tgid value that the last readdir call couldn't 2117 /* f_version caches the tgid value that the last readdir call couldn't
2124 * return. lseek aka telldir automagically resets f_version to 0. 2118 * return. lseek aka telldir automagically resets f_version to 0.
2125 */ 2119 */
2126 next_tgid = filp->f_version; 2120 tgid = filp->f_version;
2127 filp->f_version = 0; 2121 filp->f_version = 0;
2128 for (;;) { 2122 for (task = first_tgid(tgid, nr);
2129 nr_tgids = get_tgid_list(nr, next_tgid, tgid_array); 2123 task;
2130 if (!nr_tgids) { 2124 task = next_tgid(task), filp->f_pos++) {
2131 /* no more entries ! */ 2125 int len;
2126 ino_t ino;
2127 tgid = task->pid;
2128 len = snprintf(buf, sizeof(buf), "%d", tgid);
2129 ino = fake_ino(tgid, PROC_TGID_INO);
2130 if (filldir(dirent, buf, len, filp->f_pos, ino, DT_DIR) < 0) {
2131 /* returning this tgid failed, save it as the first
2132 * pid for the next readir call */
2133 filp->f_version = tgid;
2134 put_task_struct(task);
2132 break; 2135 break;
2133 } 2136 }
2134 next_tgid = 0; 2137 }
2138 return 0;
2139}
2135 2140
2136 /* do not use the last found pid, reserve it for next_tgid */ 2141/*
2137 if (nr_tgids == PROC_MAXPIDS) { 2142 * Find the first tid of a thread group to return to user space.
2138 nr_tgids--; 2143 *
2139 next_tgid = tgid_array[nr_tgids]; 2144 * Usually this is just the thread group leader, but if the users
2140 } 2145 * buffer was too small or there was a seek into the middle of the
2146 * directory we have more work todo.
2147 *
2148 * In the case of a short read we start with find_task_by_pid.
2149 *
2150 * In the case of a seek we start with the leader and walk nr
2151 * threads past it.
2152 */
2153static struct task_struct *first_tid(struct task_struct *leader, int tid, int nr)
2154{
2155 struct task_struct *pos = NULL;
2156 read_lock(&tasklist_lock);
2141 2157
2142 for (i=0;i<nr_tgids;i++) { 2158 /* Attempt to start with the pid of a thread */
2143 int tgid = tgid_array[i]; 2159 if (tid && (nr > 0)) {
2144 ino_t ino = fake_ino(tgid,PROC_TGID_INO); 2160 pos = find_task_by_pid(tid);
2145 unsigned long j = PROC_NUMBUF; 2161 if (pos && (pos->group_leader != leader))
2162 pos = NULL;
2163 if (pos)
2164 nr = 0;
2165 }
2146 2166
2147 do 2167 /* If nr exceeds the number of threads there is nothing todo */
2148 buf[--j] = '0' + (tgid % 10); 2168 if (nr) {
2149 while ((tgid /= 10) != 0); 2169 int threads = 0;
2170 task_lock(leader);
2171 if (leader->signal)
2172 threads = atomic_read(&leader->signal->count);
2173 task_unlock(leader);
2174 if (nr >= threads)
2175 goto done;
2176 }
2150 2177
2151 if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) { 2178 /* If we haven't found our starting place yet start with the
2152 /* returning this tgid failed, save it as the first 2179 * leader and walk nr threads forward.
2153 * pid for the next readir call */ 2180 */
2154 filp->f_version = tgid_array[i]; 2181 if (!pos && (nr >= 0))
2155 goto out; 2182 pos = leader;
2156 } 2183
2157 filp->f_pos++; 2184 for (; pos && pid_alive(pos); pos = next_thread(pos)) {
2158 nr++; 2185 if (--nr > 0)
2159 } 2186 continue;
2187 get_task_struct(pos);
2188 goto done;
2160 } 2189 }
2161out: 2190 pos = NULL;
2162 return 0; 2191done:
2192 read_unlock(&tasklist_lock);
2193 return pos;
2194}
2195
2196/*
2197 * Find the next thread in the thread list.
2198 * Return NULL if there is an error or no next thread.
2199 *
2200 * The reference to the input task_struct is released.
2201 */
2202static struct task_struct *next_tid(struct task_struct *start)
2203{
2204 struct task_struct *pos;
2205 read_lock(&tasklist_lock);
2206 pos = start;
2207 if (pid_alive(start))
2208 pos = next_thread(start);
2209 if (pid_alive(pos) && (pos != start->group_leader))
2210 get_task_struct(pos);
2211 else
2212 pos = NULL;
2213 read_unlock(&tasklist_lock);
2214 put_task_struct(start);
2215 return pos;
2163} 2216}
2164 2217
2165/* for the /proc/TGID/task/ directories */ 2218/* for the /proc/TGID/task/ directories */
2166static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldir) 2219static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldir)
2167{ 2220{
2168 unsigned int tid_array[PROC_MAXPIDS];
2169 char buf[PROC_NUMBUF]; 2221 char buf[PROC_NUMBUF];
2170 unsigned int nr_tids, i;
2171 struct dentry *dentry = filp->f_dentry; 2222 struct dentry *dentry = filp->f_dentry;
2172 struct inode *inode = dentry->d_inode; 2223 struct inode *inode = dentry->d_inode;
2224 struct task_struct *leader = proc_task(inode);
2225 struct task_struct *task;
2173 int retval = -ENOENT; 2226 int retval = -ENOENT;
2174 ino_t ino; 2227 ino_t ino;
2228 int tid;
2175 unsigned long pos = filp->f_pos; /* avoiding "long long" filp->f_pos */ 2229 unsigned long pos = filp->f_pos; /* avoiding "long long" filp->f_pos */
2176 2230
2177 if (!pid_alive(proc_task(inode))) 2231 if (!pid_alive(leader))
2178 goto out; 2232 goto out;
2179 retval = 0; 2233 retval = 0;
2180 2234
@@ -2193,21 +2247,25 @@ static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldi
2193 /* fall through */ 2247 /* fall through */
2194 } 2248 }
2195 2249
2196 nr_tids = get_tid_list(pos, tid_array, inode); 2250 /* f_version caches the tgid value that the last readdir call couldn't
2197 2251 * return. lseek aka telldir automagically resets f_version to 0.
2198 for (i = 0; i < nr_tids; i++) { 2252 */
2199 unsigned long j = PROC_NUMBUF; 2253 tid = filp->f_version;
2200 int tid = tid_array[i]; 2254 filp->f_version = 0;
2201 2255 for (task = first_tid(leader, tid, pos - 2);
2202 ino = fake_ino(tid,PROC_TID_INO); 2256 task;
2203 2257 task = next_tid(task), pos++) {
2204 do 2258 int len;
2205 buf[--j] = '0' + (tid % 10); 2259 tid = task->pid;
2206 while ((tid /= 10) != 0); 2260 len = snprintf(buf, sizeof(buf), "%d", tid);
2207 2261 ino = fake_ino(tid, PROC_TID_INO);
2208 if (filldir(dirent, buf+j, PROC_NUMBUF-j, pos, ino, DT_DIR) < 0) 2262 if (filldir(dirent, buf, len, pos, ino, DT_DIR < 0)) {
2263 /* returning this tgid failed, save it as the first
2264 * pid for the next readir call */
2265 filp->f_version = tid;
2266 put_task_struct(task);
2209 break; 2267 break;
2210 pos++; 2268 }
2211 } 2269 }
2212out: 2270out:
2213 filp->f_pos = pos; 2271 filp->f_pos = pos;